Git Essentials for day-to-day version control

Having a backup of our code and history of changes has saved my skin several times. Git is a complex and powerful tool, but for every use, knowing a few commands will get the job done in the vast majority of situations.

Git Essentials for day-to-day version control
A diagram visualizing git commits & branches. Source: Atlassian

I've worked in software development roles for about a decade now, but one tool I've needed in each of my jobs was git. Having a backup of our code and history of changes has saved my skin several times. Git is a complex and powerful tool, but for every use, knowing a few commands will get the job done in the vast majority of situations.

Git allows you to track changes to files and folders in your project by saving them to a local repository or 'repo'. When you initialize a repository in a directory, you ask Git to maintain snapshots of your code. Each time you commit changes you've made, Git will make a snapshot of all the changed files at that moment and stores a reference to that snapshot. This enables you to revert to previous versions of your project, compare changes over time, and collaborate with others without overwriting each other's work. Since only the changes are stored in each commit, the overall size of a repository can stay manageable. Git is local first, meaning you don't need an active internet connection to use it. However, it's most powerful when you use a remote repository to which you can push your changes.

Cloning a remote repository

When you start a new project you either work with an existing repository or begin from scratch. An existing repository can be 'cloned', if it can be found in a remote repository. For this you need to know the address of the repo either provided in https or ssh format. Optionally you might also need to deal with authentication if you're trying to clone a private repo, but we'll focus on public repositories for now. You can clone a repo with the following command:

git clone https://my-project.com

This will create a folder my-project in your current directory and clone the remote repository there. Afterwards, you can navigate into this repository using cd:

cd my-project

Initializing a new repository

In case you're starting from scratch, you need to create a new repository. This is done by running the following command in your terminal:

git init my-project

This will create a new directory called my-project, which will contain all of your code and version control information. You can navigate to this directory using the cd command:

cd my-project

Get your bearings

To see the current status of the repo, see staged and unstages files, you have a handy command:

git status

This will print out a summary of your current branch.

Adding Files to a git repository

After you have created your Git repository, either by initializing a new one (git init) or by cloning an existing one (git clone), your next task is to start working on the files within this repository. However, before your changes can be recorded in the repository's history, they need to be staged.

What is Staging?

  • Staging is the process of preparing and organizing your changes before they are committed.
  • When you stage changes, you are essentially telling Git, "These are the changes I want to save in my next commit."

Staging Changes: After creating your Git repository, the next step is to add files to it. Adding files to a Git repository involves two stages: staging and committing.

Staging a file: To stage your changes, use the git add command. For example, to stage a file named my-file.txt, you would use:

git add my-file.txt

This command stages all the changes made in my-file.txt, preparing them to be committed.

Partial Staging: If you want to stage only specific changes within a file, you can use the -p option (meaning 'patch'). This will present an interactive interface to selectively stage changes within file.

git add -p

Staging everything: If you want the nuclear option, the -A argument will stage all files not ignored by the .gitignore files.

git add -A

Staging is a powerful feature because it allows you granular control over what changes you include in a commit. You can choose to commit all your changes at once, or break them up into smaller, more focused commits.

Ignoring files and folders with .gitignore

Files and folders added to .gitignore will be ignored and not taken into account when staging & committing changes IF you haven't yet committed the files. The main purpose of .gitignore is to avoid cluttering your repository with temporary files, which may be created by your operating system or your code editor.

Remove committed files from git history

If you already committed files to git that you want to 'forget' and add to .gitignore (for example temp files, sensitive files not meant to be public), you can use the git rm --cached command. The following command will remove my-file.txt from git history & past snapshots:

git rm --cached my-file.txt

Committing Changes

Once your files are staged, you can commit them to the repository. This is done by running the following command:

git commit -am "Initial commit"

This will create a new commit with the message "Initial commit", which describes the changes you have made. You can customize this message using the -m option, and you can also specify additional options to control how the commit is created, such as including the state of your working directory (git commit -a) or signing the commit (git commit -G "John Doe <[email protected]>").

Pulling Changes from your Current Branch

If you're working on a branch together with others, when one of you makes a commit and pushes it to the remote repository, the other will have to pull those changes before pushing.

First, make sure you've commited your changes before pulling. Afterwards, run this command:

git pull --rebase

This will pull the latest changes in the remote repository and automatically merge them into your local copy. Passing the --rebase argument makes sure that instead of putting each commit, regardless of origin into a linear commit history, it will treat your local changes as coming after the remote changes. This can make your git history easier to understand later.

If there are conflicts when doing a pull

If you both made changes to the same parts of code in the same files, there will be conflicts that git cannot automatically resolve for you. In these situations, you will get merge conflicts, which will look something like this:

<<<<<<< HEAD
your code
=======
someone else's code
>>>>>>> feature/remote_branch

These are resolvable with any text editor. The goal is to find if you need to keep version from the HEAD (current changes in your local copy of the code) or the incoming code (the remote branch). It's possible you'll need to manually create the right version of the code if both changes are necessary.

💡
To avoid frequent merge conflicts, using dedicate branches per developer or per feature is recommended.

Pulling Updates from Other Branches

If someone else has made changes to a repository that you are working on, you can pull those changes into your own repository by running the following command:

git fetch origin my-branch
git merge FETCH_HEAD

This will fetch all of the changes from the origin/my-branch branch and merge them into your local my-branch branch. If there are any conflicts between the two branches, Git will prompt you to resolve them before the merge can proceed.

Merging Branches

If you are working on a project with other developers, you will likely need to merge changes from one branch into another. Git makes this process easy by allowing you to create new branches and merge them back together. To create a new branch, you can run the following command:

git checkout -b my-new-branch

This will create a new branch called my-new-branch, which you can use to make changes without affecting the master branch. Once you have made your changes, you can merge them back into the master branch by running the following command:

git checkout master
git merge my-new-branch

This will merge the changes from my-new-branch into the master branch. If there are any conflicts between the two branches, Git will prompt you to resolve them before the merge can proceed.

Resolving merge conflicts

Merge conflicts arise when a file is changed, staged and committed in both your local branch and in another branch you'd like to merge with it. This can happen when others are working on the branch you want to merge. In this case, git will try and merge the files, but if the same sections are changes in two separate branches, these conflicts will be marked like this:

<<<<<<< HEAD (current change)
This is the content in the main branch.
=======
This is the different content in the feature branch.
>>>>>>> feature-branch (incoming change)

At this point, you need to decide which changes to keep, modify, or merge. After editing, save the file and remove the conflict markers. Some code editors, like Visual Studio Code will do this automatically.

Pushing Changes Back to the Server

Once you have made changes to your repository and merged them back into the master branch (or another branch), you can push those changes back to the server by running the following command.

If you clone the repository, your remote url is already configured and you can directly run:

git push

This will push all of the changes from your local branch to the remote. If you created your git repo locally, you will need to specify a remote git repo where you want to push your changes. After creating a repo on Github for example, you can use the following command:

git remote add origin {remote_url}

The remote url will be provided after you create the repo. You might need to configure authentication before you can push to the repo if it's configured to be private.

Deleting Branches

Once you get into development and work with feature branches frequently, you might get into a situation where too many branches are present on your local machine. In this case you might want to delete them using the following command:

git branch -d {branch_name}

If the branch has been pushed, this will remove your local copy.

In case the branch has NOT been posted, for example in case you tried something out on a new branch and you want to discard your changes, you need to use -D:

git branch -D {branch_name}

Stashing your changes

In case you have made some changes that you want to quickly compare to the last commit, you can 'stash' your changes temporarily, than retrieve them after you're done with checking the original code.

git stash

This will store your changes and return the code the state of the last commit. Ready to resume work? Use the command to get your changes back:

git stash pop

Cheat sheet

Here's a table of the commands mentioned in the article.

Command Description
git init {project_name} Initialize a new Git repository or reinitialize an existing one. This command creates a new .git directory in your current working directory, setting up the necessary Git metadata and making the directory ready to track changes.
git clone {url} Clone a repository into a new directory on your local machine.
git status Display the state of the working directory and staging area, showing staged, unstaged, and untracked files.
git add {file} Stage specified files for the next commit.
git add -p Interactively stage parts of files for the next commit, allowing you to select specific changes.
git add -A Stage all changes in the entire working tree for the next commit, including new files and deletions.
git rm --cached my-file.txt Unstage and remove a file from the working directory, but keep it in your local file system.
git commit -am "Initial commit" Commit all staged changes to the repository with a message, skipping the staging area for already tracked files.
git pull --rebase Pulls the latest commits from your current branch. --rebase will make sure that you keep a linear commit history, inserting your local commits after the incoming remote changes.
git fetch origin my-branch Download objects and refs from another repository, specifically fetching a branch from the origin remote.
git merge FETCH_HEAD Merge changes fetched from a remote repository into the current branch.
git checkout -b my-new-branch Create and switch to a new branch.
git checkout master Switch to the 'master' branch.
git merge my-new-branch Merge the specified branch into the current branch.
git push Update remote refs along with associated objects.
git remote add origin {remote_url} Add a new remote repository to your current project. This command sets up a new connection to a remote repository located at {remote_url}, and names it 'origin'. This is typically used to connect your local repository to a remote server like GitHub, GitLab, or Bitbucket, allowing you to push and pull changes from and to the remote repository.
git branch -d {branch_name} Delete a branch safely (fails if the branch is not fully merged).
git branch -D {branch_name} Forcefully delete a branch, even if it has unmerged changes.
git stash Temporarily store all modified tracked files and some untracked files.
git stash pop Apply stashed changes back onto your working copy and then drop them from your stash.

Conclusions

Git is an essential part of version control and keeping your code safe! Use it for every project you create, no matter how small. Believe me, it will save you plenty of headaches once you get the hand of it.

If you like what I wrote, buy me a beer: