Git Techniques


| October 4, 2023 | in

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

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:

  1. Untracked: Files that Git does not know about.
  2. Staged: Files that have been added but not yet committed.
  3. 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:

  1. Make sure you are in the branch you want to merge into (git checkout main).
  2. Make sure you don’t have any unsaved changes.
  3. Run git merge your-branch-name.
  4. 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 just s) 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.

Related posts