Git Techniques
Git is an indispensable tool for managing source code in modern software development. While basic commands like git clone
, git add
, and git commit
are essential, they are just the tip of the iceberg. This guide is intended for developers who already have some Git experience and wish to unlock its full capabilities.
We will focus mainly here on command line actions. While the command line is powerful, you can also use a visual interface for Git operations. Tools like Sublime Merge or GitKraken offer a graphical way to perform commits, branches, merges, and more. These tools are particularly helpful for getting a visual overview of your code changes and repository history.
Quick Review of Basics
Setting Up Your Git Repository
When you’re starting a new project, the first step is to initialize a new Git repository. To do this, navigate to the root folder of your project in the command line and run:
git init
This command creates a .git
directory that contains all your configuration files and information.
Ignoring Unwanted Files
Once your repository is initialized, you’ll probably find files and folders you don’t want to track. For instance, you may have temporary debug
files or an object
folder that changes all the time. Git allows you to specify which files to ignore using a .gitignore
file. Create this file in the root directory of your project and specify the patterns for the files you want to ignore. For example:
# .gitignore
*object/
This tells Git to ignore all files in any folder named “object”. The .gitignore file uses glob matching patterns, which you can find in the online documentation.
Committing Code
Commits are snapshots of your repository at a point in time. When you commit changes, you are essentially saying, “I want to save this current state of all my files.” In Git, files can be in three states:
- Untracked: Files that Git does not know about.
- Staged: Files that have been added but not yet committed.
- Committed: Files that have been added and committed.
To commit changes, you first need to stage the files. Use git add <file>
or git add
to add all the files. Then run git commit -m 'Your commit message here'
to commit those changes.
Effective Commit Messages
When committing code, your commit messages should be clear and concise, describing what changes were made and why. A best practice is to write a short one-liner as the main commit message, followed by a more detailed explanation after a blank line. This makes it easy to understand the purpose of each commit at a glance.
git commit -m "Add looping logic
Adds a loop to go through all items in a list. This is necessary for feature XYZ."
Branching and Merging
Branching is one of Git’s most powerful features. It allows you to isolate new changes from the main codebase until they’re ready to be integrated. To create a new branch, use:
git switch -c your-branch-name
This will create a new branch and switch to it. I have found it’s good to prefix your branch name with your user id and a slash to help keep straight which branches are yours and which might be from other developers, such as <username>/<yourbranchname>
.
To merge your changes back into the main branch:
- Make sure you are in the branch you want to merge into (
git checkout main
). - Make sure you don’t have any unsaved changes.
- Run
git merge your-branch-name
. - If there are any conflicts, you will need to resolve them before completing the merge. Make sure your code builds and runs after you resolve conflicts but before completing the merge. Completing the merge consists of committing the changes.
Optimizing Remote Operations
Remote repositories (like those on GitHub, GitLab, Azure DevOps, etc.) allow you to collaborate with others and serve as a backup of your local repository. However, frequent pushing to a remote can take up build resources and complicate history. Limit your pushes to when your feature is complete and ready for a pull request (PR).
Amending Your Last Commit
We’ve all been there: you just made a commit, and then you realize you forgot to include something important, such as a comment or a tiny change in the code. Instead of making another commit, you can simply amend your last one, assuming you haven’t pushed it to the remote yet.
To amend your last commit, make the necessary changes to your working directory, stage them, and then use the following command:
git commit --amend
This will effectively rewrite the last commit to include the new changes. This is extremely handy for adding forgotten comments, fixing typos, and even correcting your commit message.
Interactive Rebasing: The Swiss Army Knife of Git
Interactive rebase is one of Git’s most powerful features and is underused in the professional world. It allows you not just to squash multiple commits into one but also to reorder them, edit the commit messages, or even split them into smaller commits. Usage of this command also requires you to have not pushed to your remote yet, as it can also rewrite history. This is another argument for not pushing every time you have a commit ready.
To perform an interactive rebase, you’ll need to specify a range of commits that you want to modify. Use the following command, where HEAD~n
specifies the last n
commits that you want to rework:
git rebase -i HEAD~n
This will open an interactive editor (often Vim, by default) where you can specify what you want to do with each commit.
- Use
pick
to keep the commit as is. - Use
reword
to change the commit message. - Use
edit
to amend the commit. - Use
squash
(or justs
) to squash the commit into the one above it.
Interactive rebase also makes it easier to sync your local branches with the remote. Before pushing your changes, you can rebase them on top of the latest changes in your main development branch. This avoids the extra merge commit that you get when you use git merge
, making your commit history linear and clean.
The Power of Squashing
Squashing commits is useful for combining several minor changes into a single meaningful commit. You often end up with multiple commits when you’re working on a feature or fix, but you might not want to keep all those tiny, incremental changes in your history. Squashing them into one commit makes your history cleaner and easier to understand.
Remember that squashing is best done before you’ve pushed commits to a shared branch, as it will rewrite commit history.
Git Reflog: Your Safety Net
Git has a “safety net” feature known as the reflog
. Essentially, the reflog
keeps a record of all Git activities in a local repository. So, even if you mess things up, chances are you can undo the damage. However, the reflog
is not permanent and only resides in your local machine. Therefore, you must act promptly to restore your repository to the desired state.
Git Reset: Soft and Hard
The git reset
command is versatile. If you’ve just made a work-in-progress commit and want to undo it while keeping the changes staged, use git reset --soft
. It’s not scary until you use the --hard
flag, which could potentially ruin your day if not used carefully.
Git Stash: A Temporary Saver
Another feature of Git is git stash
. It allows you to save your changes temporarily so you can switch between branches without committing. To retrieve the stashed changes, just use git stash pop
.
Importance of Clean Commits
Keeping your commits clean is more than just a matter of personal pride. Clean, atomic commits make code reviews easier and history easier to follow. They enable you to clearly map tasks to commits, making it easier for your team to understand what you did and why.
Best Practice: Every commit should be in a state that compiles, as it ensures your commit history is buildable and thus easier to navigate.
Workflows and Task Division
Organizing your workflow around smaller, incremental changes can help keep your history clean. For instance, if you are adding Data Transfer Objects (DTOs), make those additions in their own commit. Then, when you implement the usage of those DTOs, do that in another commit. This approach ensures that each commit is a logical unit of work that is easy to understand and review.
Handling Mid-Task Changes
Sometimes you might start with one function signature and realize you need another. In such cases, you can always update your code and make a new commit that captures this change. If you’re following good practices, your previous commits should all be buildable, so adding a new change shouldn’t disrupt the integrity of your project.
Git is a versatile and powerful tool, but it requires careful handling. It offers a range of features, from reflog
to git reset
and git stash
, to help you manage your history, fix errors, and work more efficiently. The key is to use these tools wisely and keep your commit history as clean as possible for easier navigation and review.