Skip to main content

Command Palette

Search for a command to run...

Mastering Git: A Comprehensive Guide 🚀

Updated
•15 min read
Mastering Git: A Comprehensive Guide 🚀

Introduction

Git is a powerful version control system that allows developers to manage and track changes in their codebase efficiently. This guide will delve into essential Git commands and techniques to streamline your development process. From understanding commit history to managing branches and remote repositories, let's explore the key aspects of Git.

Note: If you're new to Git, the initial encounter might feel a bit perplexing. However, by following along and revisiting this guide multiple times, you'll gradually gain confidence and grasp the concepts thoroughly. I've organized the sections in an efficient order to enhance your comprehension. Rest assured that investing time in understanding Git will greatly benefit you.


Getting Started with Git Basics

Before diving into the more advanced Git commands, let's cover the fundamental concepts that form the foundation of version control.

Initializing a Git Repository

To start using Git for version control in your project, you need to create a Git repository. This can be done using the git init command. Navigate to your project's directory and execute the following command

git init

This command initializes an empty Git repository in the current directory. Once initialized, the directory will contain the necessary Git configuration and metadata to track changes in your project.

Staging and Committing Changes

Once you've made changes to your project files, you can use Git to track and manage those changes. The process involves two main steps: staging and committing.

Staging:

To stage changes for commit, use the git add command. This tells Git which changes you want to include in the next commit. For example:

git add file1.txt       # Stage a specific file
git add .               # Stage all changes in the directory

Checking Status:

You can check the status of your changes using the git status command. It provides information about which files are modified, staged, or untracked:

git status

Sample Output:

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file1.txt

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

Viewing Differences:

To see the differences between your working directory and the last commit, use the git diff command. This helps you review the changes before staging:

git diff               # Shows changes not yet staged
git diff --staged      # Shows changes in the staging area

Sample Output:

diff --git a/file1.txt b/file1.txt
index 1234567..89abcdef 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1,4 +1,4 @@
-Hello, old content.
+Hello, new content.

Committing:

After staging your changes, you can commit them to the repository using the git commit command. This creates a snapshot of your changes with a corresponding message to describe the commit:

git commit -m "Added new feature"

Sample Output:

[master 1234567] Added new feature
 1 file changed, 1 insertion(+), 1 deletion(-)

Checkout the guidelines for commit messages here: https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716


Viewing Commit History with Git Log

To view the commit history of your repository, you can use the git log command. This provides a chronological list of commits, including commit messages, authors, dates, and commit hashes:

Show previous commits

git log
Sample Output:
commit b5ec24aef8e74b9f56c0623a0efb6f81e0c3d9a9
Author: John Doe <john@example.com>
Date:   Wed Aug 18 15:24:47 2023 -0400

    Added new feature X

commit 8a73d5e9b9a5d14699ed82efcc2dfb82c1f2abf8
Author: Jane Smith <jane@example.com>
Date:   Tue Aug 17 10:12:36 2023 -0400

    Fixed issue #123
...

Show in a single line

git log --oneline
Sample Output:
b5ec24a Added new feature X
8a73d5e Fixed issue #123
...

Show the names of the committed files as well

git log --oneline --name-only
Sample Output:
b5ec24a Added new feature X
src/file1.js
src/file2.css

8a73d5e Fixed issue #123
src/file3.js

f9c7a21 Updated documentation
docs/readme.md
docs/api.md
...

Collaborating with Remote Repositories

Git facilitates effortless teamwork through remote repositories, allowing multiple developers to collaborate on the same project even if they are not in the same location. To establish this connection, you use a connection string, which is basically the URL of the remote repository. This URL typically ends with ".git" and serves as the pathway for your local repository to communicate with the remote one. This connection is a crucial step in ensuring that changes made by different team members can be shared and synchronized effectively.

Add remote git repo to existing local git repo

# git remote add [ALIAS] [CONNECTION_STRING]
git remote add origin https://github.com/yourusername/yourrepository.git

Clone Remote Repository as a new local repo

The git clone command is used to create a copy of a remote Git repository on your local machine. This allows you to start working with the repository, make changes, and synchronize with the remote repository as needed. Both SSH and HTTP URLs are supported.

git clone git@github.com:username/repository.git

Sample Output:

Cloning into 'repository'...
remote: Enumerating objects: 200, done.
remote: Counting objects: 100% (200/200), done.
remote: Compressing objects: 100% (150/150), done.
remote: Total 200 (delta 50), reused 180 (delta 30), pack-reused 0
Receiving objects: 100% (200/200), done.
Resolving deltas: 100% (50/50), done.

List all remote repositories linked to the current local git repo

git remote -v

Sample Output:

origin  https://github.com/yourusername/yourrepository.git (fetch)
origin  https://github.com/yourusername/yourrepository.git (push)

Pushing commits to the remote repo

Code is pushed to the remote repo to keep local and remote code in sync

# git push [REMOTE_ALIAS] [BRANCH_NAME]
git push origin new-branch

Navigating and Managing Branches

Imagine you're working on a big puzzle with your friends. Instead of messing up the whole puzzle while trying different pieces, you decide to make copies of the puzzle and try different pieces on each copy. These copies are like branches in Git. You can experiment on them without worrying about ruining the original puzzle.

When you're sure that the pieces you tried on one copy are correct, you can put those pieces on the original puzzle. This is like merging your changes from a branch back to the main work. Git helps you manage these copies and changes in a smart way so that everyone can work together on the puzzle without making a mess. Branching is a core concept in Git, enabling parallel development efforts.

List branches

git branch

Sample Output:

  master
* new-branch
  feature-branch
  ...

List all branches (local + remote)

git branch -a

Sample Output:

  master
* new-branch
  feature-branch
  remotes/origin/master
  remotes/origin/development
  ...

Create a new branch named new-branch

git branch new-branch

Switch to new-branch

git checkout new-branch

Create and switch to new-branch

git checkout -b new-branch

Create a new branch named new-branch based on the existing branch existing-branch and switch to new-branch

git checkout -b new-branch existing-branch

Delete new-branch

git branch -d new-branch

Visualize the commit history along with the branch they were committed on in graph form

git log --graph --decorate

Sample Output:

* commit 7a2c9f3 (HEAD -> master)
| Author: John Doe <john@example.com>
| Date:   Mon Aug 15 09:32:27 2023 -0400
| 
|     Fix critical bug in feature A
| 
* commit 43e8ab2
| Author: Jane Smith <jane@example.com>
| Date:   Fri Aug 12 17:55:13 2023 -0400
| 
|     Add new feature B
| 
| * commit 05b7f6c (feature-A)
| | Author: Alice Johnson <alice@example.com>
| | Date:   Wed Aug 10 11:21:09 2023 -0400
| | 
| |     Implement feature A
| | 
| * commit f1a2d8e
|/  Author: Bob Williams <bob@example.com>
|   Date:   Tue Aug 9 08:12:37 2023 -0400
|   
|       Initial commit
...

Merging Changes with Git Merge

Merging combines different lines of development. Git offers two merge modes:

  • Fast-forward (--ff-only): Imagine you and a friend are working on the same document. You make a copy of the document, work on it separately, and your friend also makes changes to their copy. When your friend is done and you want to combine your changes, if they haven't made any new changes since you started, you can simply take their copy and add your changes to it. This is like a fast-forward merge. It's easy and quick because you're just adding your changes to the latest version.
  • No Fast-forward: Now, let's say your friend made more changes to their copy after you started working. When you're done and want to combine your changes, it's a bit more complex. You need to carefully figure out where your changes fit in with their new changes. This is like a no fast-forward merge. Git helps you blend both sets of changes together, creating a new version that includes everyone's work, even if things got a bit more tangled.

So, in simple terms, fast-forward merge is like adding your changes to the latest version, and no fast-forward merge is like carefully combining changes when things have changed on both sides.

Merge new-branch into master

git checkout master
git merge new-branch

Pulling Changes with git pull: Combining Fetch and Merge

The git pull command plays a crucial role in keeping your local repository up-to-date with changes from a remote repository. It's essentially a combination of two separate steps: fetching changes and merging them into your current branch. This ensures that your local branch reflects the latest state of the remote repository.

Fetching Changes

When you run git pull, the first thing it does is perform a git fetch. This action retrieves all the new commits and references from the remote repository, effectively updating your local tracking branches to match the remote branches. This is an essential step as it helps you understand what changes have been made on the remote repository without automatically integrating them into your working branch.

Merging Changes

After the fetch, git pull takes an additional step: merging the fetched changes into your current branch. This is where the git merge part comes into play. If there are new commits on the remote branch you're tracking, git pull will attempt to automatically merge those changes into your local branch. If the merge can be completed automatically without conflicts, it will do so, effectively updating your local branch with the latest changes.

Handling Conflicts

In cases where there are conflicting changes between your local branch and the remote branch, git pull will pause and prompt you to resolve the conflicts manually. You'll need to edit the conflicting files, choose the desired changes, and then complete the merge using git commit.

Using git pull

The basic syntax for using git pull is as follows:

git pull <remote_name> <branch_name>

For instance, to pull changes from the remote repository named origin and integrate them into your current branch main, you would use:

git pull origin main

Sample Output:

From https://github.com/your-username/your-repository
 * branch            main       -> FETCH_HEAD
Updating abc1234..def5678
Fast-forward
 file.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

By combining the actions of fetching and merging, git pull streamlines the process of updating your local repository with the latest changes from the remote repository. However, keep in mind that frequent use of git pull may lead to a complex history if not managed carefully. It's always a good practice to commit your local changes before pulling to ensure a smoother integration of updates.


Managing Revisions with Reset and Revert

Managing revisions involves undoing changes or resetting commits:

Revert a commit

git revert COMMIT_HASH

It creates a new commit that reverts all the changes of the given commit

Note: This will not delete the commit from the history

Reset a commit

Resetting a commit removes the commit from history and does not create a new commit.

Soft Reset

Using this option, we still have the changes in the working area

git reset --soft HEAD~1

To check the reset changes: git status

Hard Reset

This will reset the commit without saving any of the changes in the working area

git reset --hard HEAD~1

Restoring Files

To discard changes in specific files and restore them to their state in a specific commit, you can use the git restore command. This can be particularly useful when you only want to undo changes in specific files without affecting the entire commit:

git restore --source=<COMMIT_HASH> <FILE_PATH>

Sample Output:

Restored <FILE_PATH> from commit <COMMIT_HASH>

Stashing Changes for Later with Git Stash

Sometimes you're making changes to your code but suddenly need to switch to a different task or branch. Rather than committing incomplete changes, which can clutter your commit history, Git offers a handy solution called stash.

Git stash provides a way to temporarily store your working directory changes without committing them. This allows you to switch branches, work on a different task, or perform other operations without affecting your current changes. Once you're ready to return to the original task, you can apply or pop the stash back into your working directory.

The stash acts like a stack, where you can stash multiple sets of changes sequentially. This lets you manage different sets of changes across different tasks without any risk of losing your work.

Stash a change

git stash

Pop out the latest stash back to the working area

git stash pop

List all stashes

git stash list

Sample Output:

stash@{0}: On your-branch: b5ec24a Added new feature X
stash@{1}: On master: 8a73d5e Fixed issue #123

List the contents of a specific stash (say 1)

git stash show stash@{1}

Sample Output:

 src/file1.js  |  5 +++++
 src/file2.css | 10 +++++++++-
 2 files changed, 14 insertions(+), 1 deletion(-)

Pop a specific stash (say 1)

git stash pop stash@{1}

Sample Output:

On branch master
Changes from stash@{1} applied
Dropped refs/stash@{1} (8a73d5e9b9a5d14699ed82efcc2dfb82c1f2abf8)

Refining Your History with Git Rebase

Git rebase is a powerful command that enables you to rewrite commit history by incorporating changes from one branch onto another. Unlike merging, which creates new merge commits, rebasing results in a linear commit history, making it ideal for maintaining a clean and organized history.

How Git Rebase Works

When you perform a rebase, Git identifies the common ancestor of the two branches (the branch you're rebasing onto and the branch you're rebasing from). It then applies each commit from the branch you're rebasing onto, followed by the commits from the branch you're rebasing from, effectively placing your changes on top of the target branch.

Rebasing Process

  • Choose the Base Branch: Start by checking out the branch you want to rebase onto. For example, if you're on the feature branch and want to rebase it onto master, you'd use:
    git checkout master
    
  • Start the Rebase: Initiate the rebase with the git rebase command, followed by the name of the branch you want to rebase (e.g., feature):
    git rebase feature
    
  • Resolve Conflicts: If conflicts arise during the rebase, Git will pause and allow you to resolve them. After resolving each conflict, use git add to mark the conflict as resolved and git rebase --continue to proceed.
  • Finish the Rebase: Once all conflicts are resolved, Git will complete the rebase and apply the changes from the source branch onto the target branch.

    Sample Output:

    Applying: Add new feature
    Applying: Fix issue #123
    

Interactive Rebase

Git also offers an interactive mode for rebasing, which allows you to modify commits during the process. This is useful for squashing or splitting commits, reordering commits, and more. To initiate an interactive rebase, use:

git rebase -i <COMMIT_HASH>

In the interactive rebase, you can use the squash command to combine commits into one. This is particularly useful for cleaning up your commit history:

  • Change pick to squash or s for the commits you want to squash.
  • Save and close the editor.

For example, to squash the last two commits into one, your interactive rebase file would look like this:

pick abcdef1 First commit
squash 1234567 Second commit
squash 89abcdef Third commit

Use Cases for Rebase

  • Feature Branch Integration: Rebase is useful when you want to incorporate the latest changes from the main branch into your feature branch before merging.
  • Commit Cleanup: Interactive rebase allows you to squash or edit commits, creating a cleaner commit history.
  • Maintaining a Linear History: If you want a linear commit history instead of a merge commit history, rebase is the way to go.

With Git rebase, you can shape your commit history to be more coherent and organized. However, remember that rewriting history should be done with caution, especially in a shared repository.


Selective Commit Integration with git cherry-pick

The git cherry-pick command allows you to pick specific commits from one branch and apply them to another. This can be helpful when you want to incorporate changes from a particular commit without merging or rebasing an entire branch. Here's how to use git cherry-pick:

git cherry-pick <COMMIT_HASH>

For example, if you want to apply the changes from commit abc123 onto your current branch, you would use:

git cherry-pick abc123

Sample Output:

[feature-branch 1234567] Fix issue #123
 1 file changed, 1 insertion(+), 1 deletion(-)

Reviewing Actions with Git Reflog

The git reflog command provides a comprehensive log of all actions taken in the repository:

git reflog

Sample Output:

b5ec24a HEAD@{0}: commit: Added new feature X
8a73d5e HEAD@{1}: commit: Fixed issue #123
...

Wait! Didn't we see a similar command at the beginning? The git log, right? Yes, I know these commands might appear very similar, but there's a substantial difference in their scope and focus. While git log primarily displays commit history, providing insight into changes and commit details, git reflog offers a broader perspective by recording all repository actions, including merges, rebases, resets, and more. It serves as a comprehensive timeline of actions taken within the repository, making it invaluable for troubleshooting, recovering lost commits, and understanding how various operations impact the repository's structure and history.

Conclusion

Congratulations! You've now explored essential Git commands and techniques that will enhance your development workflow. Whether you're managing branches, collaborating with remote repositories, or mastering the art of resetting and reverting, these skills will empower you to become a Git expert. Keep practicing and experimenting to unlock even more possibilities within the world of version control.

Check out these handy cheat sheets that you can refer to whenever you're working and need to look up a command:

Remember, Git is a versatile tool that plays a pivotal role in modern software development. By mastering these commands and techniques, you'll be better equipped to manage projects efficiently, collaborate seamlessly with teammates, and navigate the complexities of version control.

Happy coding! 💻 🚀