Interactive Rebase: Clean up your Commit History

This article is part of our “Advanced Git” series. Be sure to follow Tower on Twitter or sign up for their newsletter to hear about the next articles.

Interactive Rebase is the Swiss Army knife of Git commands: lots of use cases and lots of possibilities! It’s really a great addition to any developer’s tool chain, because it lets you revise your local commit history—before you share your work with the rest of the team.

Let’s see what you can do with an interactive rebase and then look at some practical examples.

Advanced Git series:

  1. Part 1: Creating the Perfect Commit in Git
  2. Part 2: Branching Strategies in Git
  3. Part 3: Better Collaboration With Pull Requests
  4. Part 4: Merge Conflicts
  5. Part 5: Rebase vs. Merge
  6. Part 6: Interactive Rebase (You are here!)
  7. Part 7: Cherry-Picking Commits in Git (Coming soon!)
  8. Part 8: Using the Reflog to Restore Lost Commits

Rewriting your commit history

In short, interactive rebase allows you to manipulate your commit history. It’s meant for optimizing and cleaning up. You can…

  • change commit messages
  • combine multiple commits
  • split and edit existing commits
  • reorder commits
  • delete commits

Keep in mind that an interactive rebase rewrites your commit history: all of the involved commits get a new hash ID. Also, a quick reminder: commit IDs are there to identify commits—they are SHA-1 checksums. So, by changing that hash, you technically create completely new commits. This means that you shouldn’t use an interactive rebase on stuff that you’ve already pushed to a shared remote repository. Your colleagues might have based their work on these commits—and when you use interactive rebase to rewrite commit history, you are changing these base commits.

All of this means that an interactive rebase is meant to help you clean up and optimize your own local commit history before you merge (and possibly push) it back into a shared team branch.

Interactive rebase workflow

Before we take interactive rebase for a test drive, let’s look at the general workflow. This is always the same, no matter what exactly we’re doing—deleting a commit, changing a commit message, combining commits… the steps are identical.

The first step is to determine the range of commits you want to manipulate. How far back in time do you want to go? Once you have the answer, you can start your interactive rebase session. Here, you have the chance to edit your commit history. For example, you can manipulate the selected commits by reordering, deleting, combining them, and so on.

In your first step, you are always going to look at the current state of the commit history. You can use the git log command to examine a project’s history and show the commit log.

Here’s the little example repository we’re going to use throughout this article:

Showing a Git Tower app screen with the main master branch of a repo selected on the left panel, and a list of existing commits on the right panel.
Note that I’m using the Tower Git desktop GUI in some of my screenshots for easier visualization.

After you’ve examined the list, it’s time to start the work. Let’s do this step-by-step. In the examples of this article, we will do the following things:

  • First, we change an old commit’s message.
  • Secondly, we combine two old commits.
  • After that, we split one commit.
  • Finally, we delete a commit.

Change a commit message

In many cases, you’ll want to change the most recent commit. Keep in mind that there’s a shortcut for this scenario which doesn’t involve interactive rebase:

$ git commit --amend

This command can modify both the content and the message of the most recent commit, and it opens your default text editor. Here you can make your changes, save them, and quit the editor. This will not only update the commit message, but will effectively change the commit itself and write a new one.

Again, please be careful and don’t amend your last commit if you’ve already pushed it to the remote repository!

For any other commit (anything older than the most recent one), you have to perform an interactive rebase. To run git rebase interactively, add the -i option. 

The first step is to determine the base commit: the parent commit of the one you want to change. You can achieve this by using the commit’s hash ID or by doing a little bit of counting. To change the last three commit messages (or at least one of them), you can define the parent commit like this:

$ git rebase -i HEAD~3

An editor window opens and you can see all three commits you selected (and by “selected” I mean a range of commits: from HEAD all the way down to HEAD~3). Please notice the reverse order: unlike git log, this editor shows the oldest commit (HEAD~3) at the top and the newest at the bottom.

Animated screenshot showing an open terminal that edits the commit and adds a reword command on another line.

In this window you don’t actually change the commit message. You only tell Git what kind of manipulation you want to perform. Git offers a series of keywords for this—in our case, we change the word pick to reword which allows us to change the commit messages. After saving and closing the editor, Git will show the actual commit message and you can change it. Save and exit again, that’s it!

Combining two commits

In this next example, we’ll combine the two commits—“7b2317cf Change the page structure” and “6bcf266 Optimize markup”—so that they become one single commit. Again, as a first step you need to determine the base commit. And again, we have to go back to at least the parent commit:

$ git rebase -i HEAD~3

The editor window opens again, but instead of reword, we’ll enter squash. To be exact, we replace pick with squash in line 2 to combine it with line 1. This is an important bit to keep in mind: the squash keyword combines the line you mark up with the line above it!

Showing an open terminal with a squash on line 2 and giants words in red pointing to that line saying that squash combines the marked up line with the line above it.

After saving the changes and closing the window, a new editor window pops up. Why’s that? By combining two commits we are creating… well… a new commit! And this new commit wants a commit message. Enter the message, save and close the window… and you’ve successfully combined the two commits. Powerful stuff!

Finally a little “pro tip” for those of you working with the “Tower” Git desktop GUI: to perform a squash, you can simply drag and drop commits onto each other, right in the commits view. And if you want to change a commit message, simply right click the commit in question and select “Edit commit message” from the contextual menu.

Animated screenshot showing the process of dragging one commit message on top of another, squashing the message, then editing the commit message.

Deleting a commit

We’re bringing in the big guns for our final example: we are going to delete a revision from our commit history! To do this, we’re using the drop keyword to mark up the commit we want to get rid of:

drop 0023cdd Add simple robots.txt
pick 2b504be Change headlines for about and imprint
pick 6bcf266 Optimizes markup structure in index page

This is probably a good moment to answer a question you might have had for some time now: what can you do if you’re in the middle of a rebase operation and think, “Oh, no, this wasn’t such a good idea after all”? No problem—you can always abort! Just enter the following command to turn back to the state your repository was in before you initiated the rebase:

$ git rebase --abort

Changing the past

These were just a few examples of what an interactive rebase can do. There are plenty of other possibilities to control and revise your local commit history.

If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.

Happy rebasing and hacking—and see you soon for the next part in our series on “Advanced Git”!

Advanced Git series:

  1. Part 1: Creating the Perfect Commit in Git
  2. Part 2: Branching Strategies in Git
  3. Part 3: Better Collaboration With Pull Requests
  4. Part 4: Merge Conflicts
  5. Part 5: Rebase vs. Merge
  6. Part 6: Interactive Rebase (You are here!)
  7. Part 7: Cherry-Picking Commits in Git (Coming soon!)
  8. Part 8: Using the Reflog to Restore Lost Commits

The post Interactive Rebase: Clean up your Commit History appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Better Collaboration With Pull Requests

This article is part of our “Advanced Git” series. Be sure to follow us on Twitter or sign up for our newsletter to hear about the next articles!

In this third installment of our “Advanced Git” series, we’ll look at pull requests — a great feature which helps both small and larger teams of developers. Pull requests not only improve the review and the feedback process, but they also help tracking and discussing code changes. Last, but not least, pull requests are the ideal way to contribute to other repositories you don’t have write access to.

Advanced Git series:

  • Part 1: Creating the Perfect Commit in Git
  • Part 2: Branching Strategies in Git
  • Part 3: Better Collaboration With Pull Requests
    You are here!
  • Part 4: Merge Conflicts
    Coming soon!
  • Part 5: Rebase vs. Merge
  • Part 6: Interactive Rebase
  • Part 7: Cherry-Picking Commits in Git
  • Part 8: Using the Reflog to Restore Lost Commits

What are pull requests?

First of all, it’s important to understand that pull requests are not a core Git feature. Instead, they are provided by the Git hosting platform you’re using: GitHub, GitLab, Bitbucket, AzureDevops and others all have such a functionality built into their platforms.

Why should I create a pull request?

Before we get into the details of how to create the perfect pull request, let’s talk about why you would want to use this feature at all.

Imagine you’ve just finished a new feature for your software. Maybe you’ve been working in a feature branch, so your next step would be merging it into the mainline branch (master or main). This is totally fine in some cases, for example, if you’re the only developer on the project or if you’re experienced enough and know for certain your team members will be happy about it.

By the way: If you want to know more about branches and typical branching workflows, have a look at our second article in our “Advanced Git” series: “Branching Strategies in Git.”

Without a pull request, you would jump right to merging your code.

However, what if your changes are a bit more complex and you’d like someone else to look at your work? This is what pull requests were made for. With pull requests you can invite other people to review your work and give you feedback. 

A pull request invites reviewers to provide feedback before merging.

Once a pull request is open, you can discuss your code with other developers. Most Git hosting platforms allow other users to add comments and suggest changes during that process. After your reviewers have approved your work, it might be merged into another branch.

A pull request invites reviewers to provide feedback before merging.

Having a reviewing workflow is not the only reason for pull requests, though. They come in handy if you want to contribute to other repositories you don’t have write access to. Think of all the open source projects out there: if you have an idea for a new feature, or if you want to submit a patch, pull requests are a great way to present your ideas without having to join the project and become a main contributor.

This brings us to a topic that’s tightly connected to pull requests: forks.

Working with forks

A fork is your personal copy of an existing Git repository. Going back to our Open Source example: your first step is to create a fork of the original repository. After that, you can change code in your own, personal copy.

Creating a fork of the original respository is where you make changes.

After you’re done, you open a pull request to ask the owners of the original repository to include your changes. The owner or one of the other main contributors can review your code and then decide to include it (or not).

Two red database icons with gold arrows pointing at opposite directions between the database. The database on the left as a lock icon next to it that is circled in gold.

Important Note: Pull requests are always based on branches and not on individual commits! When you create a pull request, you base it on a certain branch and request that it gets included.

Making a reviewer’s life easier: How to create a great pull request

As mentioned earlier, pull requests are not a core Git feature. Instead, every Git platform has its own design and its own idea about how a pull request should work. They look different on GitLab, GitHub, Bitbucket, etc. Every platform has a slightly different workflow for tracking, discussing, and reviewing changes.

A layered collage of Git-based websites. Bitbucket is on top, followed by GitHub, then GitLab.

Desktop GUIs like the Tower Git client, for example, can make this easier: they provide a consistent user interface, no matter what code hosting service you’re using.

Animated screenshot of a pull request in the Tower application. A pull requests panel is open showing a pull request by the author that, when clicked, reveals information about that pull request on the right. The app has a dark interface.

Having said that, the general workflow is always the same and includes the following steps:

  1. If you don’t have write access to the repository in question, the first step is to create a fork, i.e. your personal version of the repository.
  2. Create a new local branch in your forked repository. (Reminder: pull requests are based on branches, not on commits!)
  3. Make some changes in your local branch and commit them.
  4. Push the changes to your own remote repository.
  5. Create a pull request with your changes and start the discussion with others.

Let’s look at the pull request itself and how to create one which makes another developer’s life easier. First of all, it should be short so it can be reviewed quickly. It’s harder to understand code when looking at 3,000 lines instead of 30 lines. 

Also, make sure to add a good and self-explanatory title and a meaningful description. Try to describe what you changed, why you opened the pull request, and how your changes affect the project. Most platforms allow you to add a screenshot which can help to demonstrate the changes.

Approve, merge, or decline?

Once your changes have been approved, you (or someone with write access) can merge the forked branch into the main branch. But what if the reviewer doesn’t want to merge the pull request in its current state? Well, you can always add new commits, and after pushing that branch, the existing pull request is updated.

Alternatively, the owner or someone else with write access can decline the pull request when they don’t want to merge the changes.

Safety net for developers

As you can see, pull requests are a great way to communicate and collaborate with your fellow developers. By asking others to review your work, you make sure that only high-quality code enters your codebase. 

If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.

Advanced Git series:

  • Part 1: Creating the Perfect Commit in Git
  • Part 2: Branching Strategies in Git
  • Part 3: Better Collaboration With Pull Requests
    You are here!
  • Part 4: Merge Conflicts
    Coming soon!
  • Part 5: Rebase vs. Merge
  • Part 6: Interactive Rebase
  • Part 7: Cherry-Picking Commits in Git
  • Part 8: Using the Reflog to Restore Lost Commits

The post Better Collaboration With Pull Requests appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Branching Strategies in Git

This article is part of our “Advanced Git” series. Be sure to follow us on Twitter or sign up for our newsletter to hear about the next articles!

Almost all version control systems (VCS) have some kind of support for branching. In a nutshell, branching means that you leave the main development line by creating a new, separate container for your work and continue to work there. This way you can experiment and try out new things without messing up the production code base. Git users know that Git’s branching model is special and incredibly powerful; it’s one of the coolest features of this VCS. It’s fast and lightweight, and switching back and forth between the branches is just as fast as creating or deleting them. You could say that Git encourages workflows that use a lot of branching and merging.

Git totally leaves it up to you how many branches you create and how often you merge them. Now, if you’re coding on your own, you can choose when to create a new branch and how many branches you want to keep. This changes when you’re working in a team, though. Git provides the tool, but you and your team are responsible for using it in the optimal way!

In this article, I’m going to talk about branching strategies and different types of Git branches. I’m also going to introduce you to two common branching workflows: Git Flow and GitHub Flow.

Advanced Git series:

  • Part 1: Creating the Perfect Commit in Git
  • Part 2: Branching Strategies in Git
    You are here!
  • Part 3: Better Collaboration With Pull Requests
    Coming soon!
  • Part 4: Merge Conflicts
  • Part 5: Rebase vs. Merge
  • Part 6: Interactive Rebase
  • Part 7: Cherry-Picking Commits in Git
  • Part 8: Using the Reflog to Restore Lost Commits

Teamwork: Write down a convention

Before we explore different ways of structuring releases and integrating changes, let’s talk about conventions. If you work in a team, you need to agree on a common workflow and a branching strategy for your projects. It’s a good idea to put this down in writing to make it accessible to all team members.

Admittedly, not everyone likes writing documentation or guidelines, but putting best practise on record not only avoids mistakes and collisions, it also helps when onboarding new team members. A document explaining your branching strategies will help them to understand how you work and how your team handles software releases.

Here are a couple of examples from our own documentation:

  • master represents the current public release branch
  • next represents the next public release branch (this way we can commit hotfixes on master without pulling in unwanted changes)
  • feature branches are grouped under feature/
  • WIP branches are grouped under wip/ (these can be used to create “backups” of your personal WIP)

A different team might have a different opinion on these things (for example on “wip” or “feature” groups), which will certainly be reflected in their own documentation.

Integrating changes and structuring releases

When you think about how to work with branches in your Git repositories, you should probably start with thinking about how to integrate changes and how to structure releases. All those topics are tightly connected. To help you better understand your options, let’s look at two different strategies. The examples are meant to illustrate the extreme ends of the spectrum, which is why they should give you some ideas of how you can design your own branching workflow:

  • Mainline Development
  • State, Release, and Feature Branches

The first option could be described as “always be integrating” which basically comes down to: always integrate your own work with the work of the team. In the second strategy you gather your work and release a collection of it, i.e. multiple different types of branches enter the stage. Both approaches have their pros and cons, and both strategies can work for some teams, but not for others. Most development teams work somewhere in between those extremes.

Let’s start with the mainline development and explain how this strategy works.

Mainline Development

I said it earlier, but the motto of this approach is “always be integrating.” You have one single branch, and everyone contributes by committing to the mainline:

Remember that we’re simplifying for this example. I doubt that any team in the real world works with such a simple branching structure. However, it does help to understand the advantages and disadvantages of this model.

Firstly, you only have one branch which makes it easy to keep track of the changes in your project. Secondly, commits must be relatively small: you can’t risk big, bloated commits in an environment where things are constantly integrated into production code. As a result, your team’s testing and QA standards must be top notch! If you don’t have a high-quality testing environment, the mainline development approach won’t work for you. 

State, Release and Feature branches

Let’s look at the opposite now and how to work with multiple different types of branches. They all have a different job: new features and experimental code are kept in their own branches, releases can be planned and managed in their own branches, and even various states in your development flow can be represented by branches:

Remember that this all depends on your team’s needs and your project’s requirements. While this approach may look complicated at first, it’s all a matter of practise and getting used to it.

Now, let’s explore two main types of branches in more detail: long-running branches and short-lived branches.

Long-running branches

Every Git repository contains at least one long-running branch which is typically called master or main. Of course, your team may have decided to have other long-running branches in a project, for example something like develop, production or staging. All of those branches have one thing in common: they exist during the entire lifetime of a project. 

A mainline branch like master or main is one example for a long-running branch. Additionally, there are so-called integration branches, like develop or staging. These branches usually represent states in a project’s release or deployment process. If your code moves through different states in its development life cycle — e.g. from developing to staging to production — it makes sense to mirror this structure in your branches, too.

One last thing about long-running branches: most teams have a rule like “don’t commit directly to a long-running branch.” Instead, commits are usually integrated through a merge or rebase. There are two main reasons for such a convention:

  • Quality: No untested or unreviewed code should be added to a production environment.
  • Release bundling and scheduling: You might want to release new code in batches and even schedule the releases in advance.

Next up: short-lived branches, which are usually created for certain purposes and then deleted after the code has been integrated.

Short-lived branches

In contrast to long-running branches, short-lived branches are created for temporary purposes. Once they’ve fulfilled their duty and the code has been integrated into the mainline (or another long-lived branch), they are deleted. There are many different reasons for creating a short-lived branch, e.g. starting to work on a new and experimental feature, fixing a bug, refactoring your code, etc.

Typically, a short-lived branch is based on a long-running branch. Let’s say you start working on a new feature of your software. You might base the new feature on your long-running main branch. After several commits and some tests you decide the work is finished. The new feature can be integrated into the main branch, and after it has been merged or rebased, the feature branch can be deleted. 

In the last section of this article, let’s look at two popular branching strategies: Git Flow and GitHub Flow. While you and your team may decide on something completely different, you can take them as inspiration for your own branching strategy.

Git Flow

One well-known branching strategy is called Git Flow. The main branch always reflects the current production state. There is a second long-running branch, typically called develop. All feature branches start from here and will be merged into develop. Also, it’s the starting point for new releases: developers open a new release branch, work on that, test it, and commit their bug fixes on such a release branch. Once everything works and you’re confident that it’s ready for production, you merge it back into main. As the last step, you add a tag for the release commit on main and delete the release branch.

Git Flow works pretty well for packaged software like (desktop) applications or libraries, but it seems like a bit of an overkill for website projects. Here, the difference between the main branch and the release branch is often not big enough to benefit from the distinction. 

If you’re using a Git desktop GUI like Tower, you’ll find the possible actions in the interface and won’t have to memorize any new commands:

Git Flow offers a couple of predefined actions: a desktop GUI like Tower can save you from learning these new commands by heart.

GitHub Flow

If you and your team follow the continuous delivery approach with short production cycles and frequent releases, I would suggest looking at GitHub Flow

It’s extremely lean and simple: there is one long-running branch, the default main branch. Anything you’re actively working on has its own separate branch. It doesn’t matter if that’s a feature, a bug fix, or a refactoring.

What’s the “best” Git branching strategy?

If you ask 10 different teams how they’re using Git branches, you’ll probably get 10 different answers. There is no such thing as the “best” branching strategy and no perfect workflow that everyone should adopt. In order to find the best model for you and your team, you should sit down together, analyze your project, talk about your release strategy, and then decide on a branching workflow that supports you in the best possible way.

If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.

Advanced Git series:

  • Part 1: Creating the Perfect Commit in Git
  • Part 2: Branching Strategies in Git
    You are here!
  • Part 3: Better Collaboration With Pull Requests
    Coming soon!
  • Part 4: Merge Conflicts
  • Part 5: Rebase vs. Merge
  • Part 6: Interactive Rebase
  • Part 7: Cherry-Picking Commits in Git
  • Part 8: Using the Reflog to Restore Lost Commits

The post Branching Strategies in Git appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Creating the Perfect Commit in Git

This article is part of our “Advanced Git” series. Be sure to follow Tower on Twitter or sign up for the Tower newsletter to hear about the next articles!

A commit in Git can be one of two things:

  • It can be a jumbled assortment of changes from all sorts of topics: some lines of code for a bugfix, a stab at rewriting an old module, and a couple of new files for a brand new feature.
  • Or, with a little bit of care, it can be something that helps us stay on top of things. It can be a container for related changes that belong to one and only one topic, and thereby make it easier for us to understand what happened.

In this post, we’re talking about what it takes to produce the latter type of commit or, in other words: the “perfect” commit.

Advanced Git series:

  • Part 1: Creating the Perfect Commit in Git
    You are here!
  • Part 2: Branching Strategies in Git
    Coming soon!
  • Part 3: Better Collaboration With Pull Requests
  • Part 4: Merge Conflicts
  • Part 5: Rebase vs. Merge
  • Part 6: Interactive Rebase
  • Part 7: Cherry-Picking Commits in Git
  • Part 8: Using the Reflog to Restore Lost Commits

Why clean and granular commits matter

Is it really necessary to compose commits in a careful, thoughtful way? Can’t we just treat Git as a boring backup system? Let’s revisit our example from above one more time.

If we follow the first path – where we just cram changes into commits whenever they happen – commits lose much of their value. The separation between one commit and the next becomes arbitrary: there seems to be no reason why changes were put into one and not the other commit. Looking at these commits later, e.g. when your colleagues try to make sense of what happened in that revision, is like going through the “everything drawer” that every household has: there’s everything in here that found no place elsewhere, from crayons to thumbtacks and cash slips. It’s terribly hard to find something in these drawers!

Following the second path – where we put only things (read: changes) that belong together into the same commit – requires a bit more planning and discipline. But in the end, you and your team are rewarded with something very valuable: a clean commit history! These commits help you understand what happened. They help explain the complex changes that were made in a digestible manner.

How do we go about creating better commits?

Composing better commits

One concept is central to composing better commits in Git: the Staging Area.

The Staging Area was made exactly for this purpose: to allow developers to select changes – in a very granular way – that should be part of the next commit. And, unlike other version control systems, Git forces you to make use of this Staging Area.

Unfortunately, however, it’s still easy to ignore the tidying effect of the Staging Area: a simple git add . will take all of our current local changes and mark them for the next commit.

It’s true that this can be a very helpful and valid approach sometimes. But many times, we would be better off stopping for a second and deciding if really all of our changes are actually about the same topic. Or if two or three separate commits might be a much better choice. 

In most cases, it makes a lot of sense to keep commits rather smaller than larger. Focused on an individual topic (instead of two, three, or four), they tend to be much more readable.

The Staging Area allows us to carefully pick each change that should go into the next commit: 

$ git add file1.ext file2.ext

This will only mark these two files for the next commit and leave the other changes for a future commit or further edits.

This simple act of pausing and deliberately choosing what should make it into the next commit goes a long way. But we can get even more precise than that. Because sometimes, even the changes in a single file belong to multiple topics.

Let’s look at a real-life example and take a look at the exact changes in our “index.html” file. We can either use the “git diff” command or a Git desktop GUI like Tower:

Now, we can add the -p option to git add:

$ git add -p index.html

We’re instructing Git to go through this file on a “patch” level: Git takes us by the hand and walks us through all of the changes in this file. And it asks us, for each chunk, if we want to add it to the Staging Area or not:

By typing [Y] (for “yes”) for the first chunk and [N] (for “no”) for the second chunk, we can include the first part of our changes in this file in the next commit, but leave the other changes for a later time or more edits.

The result? A more granular, more precise commit that’s focused on a single topic.

Testing your code

Since we’re talking about “the perfect commit” here, we cannot ignore the topic of testing. How exactly you “test” your code can certainly vary, but the notion that tests are important isn’t new. In fact, many teams refuse to consider a piece of code completed if it’s not properly tested.

If you’re still on the fence about whether you should test your code or not, let’s debunk a couple of myths about testing:

  • “Tests are overrated”: The fact is that tests help you find bugs more quickly. Most importantly, they help you find them before something goes into production – which is when mistakes hurt the most. And finding bugs early is, without exaggeration, priceless!
  • “Tests cost valuable time”: After some time you will find that well-written tests make you write code faster. You waste less time hunting bugs and find that, more often, a well-structured test primes your thinking for the actual implementation, too.
  • “Testing is complicated”: While this might have been an argument a couple of years ago, this is now untrue. Most professional programming frameworks and languages come with extensive support for setting up, writing, and managing tests.

All in all, adding tests to your development habits is almost guaranteed to make your code base more robust. And, at the same time, they help you become a better programmer.

A valuable commit message

Version control with Git is not a fancy way of backing up your code. And, as we’ve already discussed, commits are not a dump of arbitrary changes. Commits exist to help you and your teammates understand what happened in a project. And a good commit message goes a long way to ensure this.

But what makes a good commit message?

  • A brief and concise subject line that summarizes the changes
  • A descriptive message body that explains the most important facts (and as concisely as possible)

Let’s start with the subject line: the goal is to get a brief summary of what happened. Brevity, of course, is a relative term; but the general rule of thumb is to (ideally) keep the subject under 50 characters. By the way, if you find yourself struggling to come up with something brief, this might be an indicator that the commit tackles too many topics! It could be worthwhile to take another look and see if you have to split it into multiple, separate ones.

If you close the subject with a line break and an additional empty line, Git understands that the following text is the message’s “body.” Here, you have more space to describe what happened. It helps to keep the following questions in mind, which your body text should aim to answer:

  • What changed in your project with this commit?
  • What was the reason for making this change?
  • Is there anything special to watch out for? Anything someone else should know about these changes?

If you keep these questions in mind when writing your commit message body, you are very likely to produce a helpful description of what happened. And this, ultimately, benefits your colleagues (and after some time: you) when trying to understand this commit.

On top of the rules I just described about the content of commit messages, many teams also care about the format: agreeing on character limits, soft or hard line wraps, etc. all help to produce better commits within a team. 

To make it easier to stick by such rules, we recently added some features to Tower, the Git desktop GUI that we make: you can now, for example, configure character counts or automatic line wraps just as you like.

A great codebase consists of great commits

Any developer will admit that they want a great code base. But there’s only one way to achieve this lofty goal: by consistently producing great commits! I hope I was able to demonstrate that (a) it’s absolutely worth pursuing this goal and (b) it’s not that hard to achieve.

If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.

Have fun creating awesome commits!

Advanced Git series:

  • Part 1: Creating the Perfect Commit in Git
    You are here!
  • Part 2: Branching Strategies in Git
    Coming soon!
  • Part 3: Better Collaboration With Pull Requests
  • Part 4: Merge Conflicts
  • Part 5: Rebase vs. Merge
  • Part 6: Interactive Rebase
  • Part 7: Cherry-Picking Commits in Git
  • Part 8: Using the Reflog to Restore Lost Commits


The post Creating the Perfect Commit in Git appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

A Guide To Undoing Mistakes With Git (Part 2)

In this second part of our series on "Undoing Mistakes with Git", we’ll bravely look danger in the eye again: I’ve prepared four new doomsday scenarios — including, of course, some clever ways to save our necks! But before we dive in: take a look at the check out previous articles on Git for even more self-rescue methods that help you undo your mistakes with Git!

Let’s go!

Recovering a Deleted Branch Using the Reflog

Have you ever deleted a branch and, shortly after, realized that you shouldn’t have? In the unlikely event that you don’t know this feeling, I can tell you that it’s not a good one. A mixture of sadness and anger creeps up on you, while you think of all the hard work that went into that branch’s commits, all the valuable code that you’ve now lost.

Luckily, there’s a way to bring that branch back from the dead — with the help of a Git tool named "Reflog". We had used this tool in the first part of our series, but here’s a little refresher: the Reflog is like a journal where Git notes every movement of the HEAD pointer in your local repository. In other, less nerdy words: any time you checkout, commit, merge, rebase, cherry-pick, and so on, a journal entry is created. This makes the Reflog a perfect safety net when things go wrong!

Let’s take a look at a concrete example:

$ git branch
* feature/login
master

We can see that we currently have our branch feature/login checked out. Let’s say that this is the branch we’re going to delete (inadvertently). Before we can do that, however, we need to switch to a different branch because we cannot delete our current HEAD branch!

$ git checkout master
$ git branch -d feature/login

Our valuable feature branch is now gone — and I’ll give you a minute to (a) understand the gravity of our mistake and (b) to mourn a little. After you’ve wiped away the tears, we need to find a way to bring back this branch! Let’s open the Reflog (simply by typing git reflog) and see what it has in store for us:

Here are some comments to help you make sense of the output:

  • First of all, you need to know that the Reflog sorts its entries chronologically: the newest items are at the top of the list.
  • The topmost (and therefore newest) item is the git checkout command that we performed before deleting the branch. It’s logged here in the Reflog because it’s one of these "HEAD pointer movements" that the Reflog so dutifully records.
  • To undo our grave mistake, we can simply return to the state before that — which is also cleanly and clearly recorded in the Reflog!

So let’s try this, by creating a new branch (with the name of our "lost" branch) that starts at this "before" state SHA-1 hash:

$ git branch feature/login 776f8ca

And voila! You’ll be delighted to see that we’ve now restored our seemingly lost branch! 🎉

If you’re using a Git desktop GUI like "Tower", you can take a nice shortcut: simply hit CMD + Z on your keyboard to undo the last command — even if you’ve just violently deleted a branch!

Luckily, these types of problems can be easily corrected. Let’s roll up our sleeves and get to work.

The first step is to switch to the correct destination branch and then move the commit overusing the cherry-pick command:

$ git checkout feature/login
$ git cherry-pick 776f8caf

You will now have the commit on the desired branch, where it should have been in the first place. Awesome!

But there’s still one thing left to do: we need to clean up the branch where it accidentally landed at first! The cherry-pick command, so to speak, created a copy of the commit — but the original is still present on our long-running branch:

This means we have to switch back to our long-running branch and use git reset to remove it:

$ git checkout main
$ git reset --hard HEAD~1

As you can see, we’re using the git reset command here to erase the faulty commit. The HEAD~1 parameter tells Git to "go back 1 revision behind HEAD", effectively erasing the topmost (and in our case: unwanted) commit from the history of that branch.

And voila: the commit is now where it should have been in the first place and our long-running branch is clean — as if our mistake had never happened!

Editing the Message of an Old Commit

It’s all too easy to smuggle a typo into a commit message — and only discover it much later. In such a case, the good old --amend option of git commit cannot be used to fix this problem, because it only works for the very last commit. To correct any commit that is older than that, we have to resort to a Git tool called "Interactive Rebase".

First, we have to tell Interactive Rebase which part of the commit history we want to edit. This is done by feeding it a commit hash: the parent commit of the one we want to manipulate.

$ git rebase -i 6bcf266b

An editor window will then open up. It contains a list of all commits after the one we provided as a basis for the Interactive Rebase in the command:

Here, it’s important that you don’t follow your first impulse: in this step, we do not edit the commit message, yet. Instead, we only tell Git what kind of manipulation we want to do with which commit(s). Quite conveniently, there’s a list of action keywords noted in the comments at the bottom of this window. For our case, we mark up line #1 with reword (thereby replacing the standard pick).

All that’s left to do in this step is to save and close the editor window. In return, a new editor window will open up that contains the current message of the commit we marked up. And now is finally the time to make our edits!

Here’s the whole process at a glance for you:

This is where fixup comes in. It allows you to still make this correcting band-aid commit. But here comes the magic: it then applies it to the original, broken commit (repairing it that way) and then discards the ugly band-aid commit completely!

We can go through a practical example together! Let’s say that the selected commit here is broken.

Let’s also say that I have prepared changes in a file named error.html that will solve the problem. Here’s the first step we need to make:

$ git add error.html
$ git commit --fixup 2b504bee

We’re creating a new commit, but we’re telling Git this is a special one: it’s a fix for an old commit with the specified SHA-1 hash (2b504bee in this case).

The second step, now, is to start an Interactive Rebase session — because fixup belongs to the big toolset of Interactive Rebase.

$ git rebase -i --autosquash 0023cddd

Two things are worth explaining about this command. First, why did I provide 0023cddd as the revision hash here? Because we need to start our Interactive Rebase session at the parent commit of our broken fellow.

Second, what is the --autosquash option for? It takes a lot of work off our shoulders! In the editor window that now opens, everything is already prepared for us:

Thanks to the --autosquash option, Git has already done the heavy lifting for us:

  1. It marked our little band-aid commit with the fixup action keyword. That way, Git will combine it with the commit directly above and then discard it.
  2. It also reordered the lines accordingly, moving our band-aid commit directly below the commit we want to fix (again: fixup works by combining the marked-up commit with the one above!).

In short: There’s nothing to do for us but close the window!

Let’s take a final look at the end result.

  • The formerly broken commit is fixed: it now contains the changes we prepared in our band-aid commit.
  • The ugly band-aid commit itself has been discarded: the commit history is clean and easy to read — as if no mistake had occurred at all.

Knowing How to Undo Mistakes is a Superpower

Congratulations! You are now able to save your neck in many difficult situations! We cannot really avoid these situations: no matter how experienced we are as developers, mistakes are simply part of the job. But now that you know how to deal with them, you can face them with a laid-back heart rate. 💚

If you want to learn more about undoing mistakes with Git, I can recommend the free "First Aid Kit for Git", a series of short videos about exactly this topic.

Have fun making mistakes — and, of course, undoing them with ease!

A Guide To Undoing Mistakes With Git

Working with code is a risky endeavour: There are countless ways to shoot yourself in the foot! But if you use Git as your version control system, then you have an excellent safety net. A lot of “undo” tools will help you recover from almost any type of disaster.

In this first article of our two-part series, we will look at various mistakes — and how to safely undo them with Git!

Discard Uncommitted Changes in a File

Suppose you’ve made some changes to a file, and after some time you notice that your efforts aren’t leading anywhere. It would be best to start over and undo your changes to this file.

The good news is that if you haven’t committed the modifications, undoing them is pretty easy. But there’s also a bit of bad news: You cannot bring back the modifications once you’ve undone them! Because they haven’t been saved to Git’s “database”, there’s no way to restore them!

With this little warning out of the way, let’s undo our changes in index.html:

$ git restore index.html

This command will restore our file to its last committed state, wiping it clean of any local changes.

Restore a Deleted File

Let’s take the previous example one step further. Let’s say that, rather than modifying index.html, you’ve deleted it entirely. Again, let’s suppose you haven’t committed this to the repository yet.

You’ll be pleased to hear that git restore is equipped to handle this situation just as easily:

$ git restore index.html

The restore command doesn’t really care what exactly you did to that poor file. It simply recreates its last committed state!

Discard Some of Your Changes

Most days are a mixture of good and bad work. And sometimes we have both in a single file: Some of your modifications will be great (let’s be generous and call them genius), while others are fit for the garbage bin.

Git allows you to work with changes in a very granular way. Using git restore with the -p flag makes this whole undoing business much more nuanced:

$ git restore -p index.html

Git takes us by the hand and walks us through every chunk of changes in the file, asking whether we want to throw it away (in which case, we would type y) or keep it (typing n):

Using the --amend option allows you to change this very last commit (and only this one):

$ git commit --amend -m "A message without typos"

In case you’ve also forgotten to add a certain change, you can easily do so. Simply stage it like any other change with the git add command, and then run git commit --amend again:

$ git add forgotten-change.txt

$ git commit --amend --no-edit

The --no-edit option tells Git that we don’t want to change the commit’s message this time.

Revert the Effects of a Bad Commit

In all of the above cases, we were pretty quick to recognize our mistakes. But often, we only learn of a mistake long after we’ve made it. The bad commit sits in our revision history, peering snarkily at us.

Of course, there’s a solution to this problem, too: the git revert command! And it solves our issue in a very non-destructive way. Instead of ripping our bad commit out of the history, it creates a new commit that contains the opposite changes.

Performing that on the command line is as simple as providing the revision hash of that bad commit to the git revert command:

$ git revert 2b504bee

As mentioned, this will not delete our bad commit (which could be problematic if we have already shared it with colleagues in a remote repository). Instead, a new commit containing the reverted changes will be automatically created.

Restore a Previous State of the Project

Sometimes, we have to admit that we’ve coded ourselves into a dead end. Perhaps our last couple of commits have yielded no fruit and are better off undone.

Luckily, this problem is pretty easy to solve. We simply need to provide the SHA-1 hash of the revision that we want to return to when we use the git reset command. Any commits that come after this revision will then disappear:

$ git reset --hard 2b504bee

The --hard option makes sure that we are left with a clean working copy. Alternatively, we can use the --mixed option for a bit more flexibility (and safety): --mixed will preserve the changes that were contained in the deleted commits as local changes in our working copy.

The first thing to know about reflog is that it’s ordered chronologically. Therefore, it should come as no surprise to see our recent git reset mistake at the very top. If we now want to undo this, we can simply return to the state before, which is also protocoled here, right below!

We can now copy the commit hash of this safe state and create a new branch based on it:

$ git branch happy-ending e5b19e4

Of course, we could have also used git reset e5b19e4 to return to this state. Personally, however, I prefer to create a new branch: It comes with no downsides and allows me to inspect whether this state is really what I want.

Restore a Single File From a Previous State

Until now, when we’ve worked with committed states, we’ve always worked with the complete project. But what if we want to restore a single file, not the whole project? For example, let’s say we’ve deleted a file, only to find out much later that we shouldn’t have. To get us out of this misery, we’ll have to solve two problems:

  1. find the commit where we deleted the file,
  2. then (and only then) restore it.

Let’s go search the commit history for our poor lost file:

$ git log -- <filename>

The output of this lists all commits where this file has been modified. And because log output is sorted chronologically, we shouldn’t have to search for long — the commit in which we deleted the file will likely be topmost (because after deleting it, the file probably wouldn’t show up in newer commits anymore).

With that commit’s hash and the name of our file, we have everything we need to bring it back from the dead:

$ git checkout <deletion commit hash>~1 -- <filename>

Note that we’re using ~1 to address the commit before the one where we made the deletion. This is necessary because the commit where the deletion happened doesn’t contain the file anymore, so we can’t use it to restore the file.

You Are Now (Almost) Invincible

During the course of this article, we’ve witnessed many disasters — but we’ve seen that virtually nothing is beyond repair in Git! Once you know the right commands, you can always find a way to save your neck.

But to really become invincible (in Git, that is), you’ll have to wait for the second part of this series. We will look at some more hairy problems, such as how to recover deleted branches, how to move commits between branches, and how to combine multiple commits into one!

In the meantime, if you want to learn more about undoing mistakes with Git, I recommend the free “First Aid Kit for Git”, a series of short videos about this very topic.

See you soon in part two of this series! Subscribe to the Smashing Newsletter to not miss that one. ;-)

Getting The Most Out Of Git

Not a single project today will get away without some sort of version control with Git under the hood. Knowing Git well helps you become a better developer, boost your developer’s workflow and truly improve the quality of your code base. However, that requires going a little bit outside of the comfort zone that we are all familiar with. As it turns out, there is a bit more to Git than just commit, push and pull.

Some developers stay true to the main principles in Git, and often that’s absolutely understandable. In the front-end world of ours, there are just so many sophisticated things to understand and get better at, that frankly Git is often not a high priority. As a side effect, many of the valuable techniques that can boost a developer’s workflow remain unnoticed and rarely discovered.

In this article, we'll explore four advanced Git tools, and hopefully, whet your appetite to learn even more about Git!

Recovering Deleted Commits

You’re convinced that you’ve programmed yourself into a dead end because your last two commits lead nowhere! Understandably, you might want to undo them and start over.

Here’s one way to do this:

$ git reset --hard 2b504be

But let’s also say that, moments later, you notice that you made a mistake: actually, the commits contained important data and you just lost valuable work! 😱

Your heart starts pumping, the sweat starts running — you know the drill. 🤯

Now, the million-dollar question is: How can we get those seemingly deleted commits back? Luckily, there is an answer: the "Reflog"!

Using the Reflog to Recover Lost States

You can think of the Reflog as Git’s "diary": it’s the place where Git protocols every movement of the HEAD pointer. Or, in other words: all of the more interesting actions like when you commit, merge, checkout, rebase, cherry-pick and others. This, of course, makes it a perfect tool for those inevitable situations when things go wrong.

Let’s open the Reflog for our example scenario and see how it can help us:

$ git reflog

The first and most important thing to know about the Reflog is that it’s ordered chronologically. And indeed: the topmost (in other words: newest) item is our mishap when we had hastily used git reset and lost some commits.

The solution to fix our problem is pretty easy: we can simply return to the state before the mistake. And that state is clearly protocoled in the Reflog, right below our fatal reset command. To undo our mistake, we can simply use git reset once more to recover this seemingly lost state:

$ git reset e5b19e4

You can also accomplish the same result a little bit faster. As a group of friendly and passionate developers on "Tower" Git desktop GUI, we’ve been aiming to resolve common pain points around Git heads-on. So in our little tool, you can achieve the same results by simply hitting CMD + Z — as if you wanted to correct a simple typo in your text editor. In fact, the same hotkey is available for a family of different actions, e.g. when you’ve wrongfully deleted a branch, made a mistake with a merge operation, or committed something on the wrong branch.

to correct this mistake, we’ll initiate an Interactive Rebase session, starting at the faulty commit’s parent revision:

git rebase -i 2b504be

An editor window will then open and allow us to manipulate the selected part of our commit history:

In our case, since we want to delete a commit, we simply mark up the respective line in the editor with the drop action keyword. Once we hit "Save" in our editor and close the window, the Interactive Rebase is completed — and the unwanted commit will have disappeared!

(Just a quick note: again, if you’re using a desktop GUI like Tower, you can take a shortcut: simply right-click the unwanted commit and select the "Delete..." option from the contextual menu.)

As mentioned above, Interactive Rebase has a lot more use cases to offer! If you’d like to learn more about Interactive Rebase and what it can do, take a look at the free “First Aid Kit for Git”: a collection of short, helpful, and free videos, all around undoing mistakes with Git.

Using Submodules To Manage Third-Party Code

In today’s complex software world, there’s hardly a project that doesn't include code from other sources: a module or library from a third party or even from your own team. Managing these modules in an elegant, pragmatic way greatly helps to reduce headaches!

In theory, you could simply copy and paste the necessary code files into your project and then commit them to your code base. But this comes with a couple of downsides. Most importantly, you’ll have a very hard time updating third-party code when new versions become available. You’d have to download the code again and copy-paste it — again and again.

Additionally, it’s considered bad practice if you squeeze multiple projects (your actual project and all the third-party libraries you might need) into a single Git repository. This mixes external code with our own, unique project files.

A much better way to do this is to use Git’s "Submodule" structure: this allows you to include third-party code simply as a Git repository inside your actual project’s Git repository. This means that all code bases remain neatly separated.

Including a third-party module / library is as easy as executing the following Git command:

$ git submodule add https://github.com/djyde/ToProgress

This command will download a clone of the specified Git repository into your project. You’ll be left with a fully-functional, nicely separated Git repository of your third-party code. Tidy, clean and flexible.

Submodules, admittedly, are quite a complex topic when it comes to handling them in practice. If you want to understand them a bit more thoroughly, check out the “Submodules” chapter of the free "Learn Version Control with Git" online book.

Composing Commits With Precision

There’s a golden rule in version control: a commit should only contain changes from a single issue! When you stick to this rule, you will create commits that are easy to understand. When you don’t — when multiple issues and topics get crammed into the same commit — you will inflict chaos on your code base in almost no time.

In practice, this means that you create separate commits for each and every topic. Even if it’s just a small bug fix where you’re changing only a semicolon: it’s a topic of its own, so it gets a commit of its own!

But in the messy real world, we often don’t work at only one topic at a time. Or we think we do, and only later discover that our code from the last three hours actually involves three different topics. This trickles down to individual files: often, the changes in a single file belong to multiple topics.

That’s when adding a complete file to the next commit isn’t the best strategy anymore.

Staging Selected Parts of Your Changed Files

In the example case below, you can see that we currently have two chunks (= parts or areas) of modifications in our file imprint.html:

Let’s say that the first chunk belongs to one topic (maybe we’re in the process of unifying and cleaning up all page titles in our project). And let’s also say that the second chunk belongs to another, completely unrelated topic.

A simple git add imprint.html, i.e. adding the whole file to Staging, would cram all of its changes into the same commit. Our colleagues, then, would have a hard time understanding what this particular commit was really about. Seeing some titles being changed, they might think it was only about the “title clean-up project”, but they might very well overlook the other changes.

Luckily, Git allows us to precisely select the chunks we want to put into the next commit! All we have to do is add the -p flag to our git add command:

$ git add -p imprint.html

Git now takes us by the hand and walks us through each and every chunk of changes in that file. And for each one, it asks us a simple question: “Stage this chunk?”

Let’s type Y (for “Yes”) for the first one and N for the second one. When we then actually make our commit, only the first chunk of changes will be included. The second one remains as an uncommitted local change in our working copy for a later, separate commit.

If you’re using Git in a desktop GUI, you might be able to do this right through the interface:

Becoming More Productive with Git

This short article was just a short glimpse into some of the advanced features in Git. But I sincerely hope it shows that Git has so many powerful features under the hood! From Interactive Rebase to Submodules and from the Reflog to File History, it pays to learn these advanced features because they help you become more productive and make fewer mistakes.

If you want to dive deeper, here are some helpful (and free) resources:

  • Git Cheat Sheet
    If you want to keep the most important commands at hand, the “Git Cheat Sheet" might be for you. Available in English, German, Spanish, Portuguese, Arabic and Chinese.
  • Undoing Mistakes
    Git is a perfect safety net for when things go wrong. Learning about the different “undo” features in Git is time well spent for any developer. The "First Aid Kit for Git", a collection of short videos, provides a great introduction.

Have fun becoming a better developer!

CMD+Z for Git is Here

Version control with Git has become a “commodity” by now: virtually every software project today uses Git, and virtually every developer knows Git to some extent. This explains why I sometimes hear the following question when I talk about what I do for a living: “A desktop client for Git? Why would I need that? I can do that on the command line!

If I’m lucky, I have a computer next to me when that question hits me. And instead of producing a wordy answer, I do a couple of things in Tower, our desktop client for Git.

Anyone who’s ever performed an “Interactive Rebase” is amazed by how easy that is in Tower: you simply drag and drop to combine commits or reorder them. And anyone who hasn’t yet used “Interactive Rebase” – because it was too complicated and clunky to use on the command line – now understands that it’s a very valuable tool.

Or I might make a mistake: accidentally deleting a branch or messing up a merge in a terrible way. And I’ll simply hit CMD+Z, like I would in a text editor, to undo the mess I just made.

People then start to realize that the real question is not: “Can I get by using Git on the command line?” The more important questions are:

  • Can I use all of Git’s power? (Even the advanced features that might be hard to use, but that can be very valuable…)
  • Can I work with Git in a productive way? (Not having to look up parameters, not spending too much time on my workflows…)
  • Can I work with Git in an easy way? (Without having to think about it too much…)

We – a small team of just 9 people – have spent the last 10 years answering those questions by building Tower.

How to Undo Mistakes

One of Git’s greatest features is that it allows you to undo almost anything. However, it requires quite some experience to know how exactly to undo your particular kind of mess: a failed merge has to be cleaned up differently than a deleted branch!

After working on this for a long time, Tower now allows you to undo virtually anything – simply by pressing CMD+Z! (Note: the “Undo” feature is a quite recent addition and first available in the Mac version of Tower. It will soon come to Windows, too).

No matter if you messed up a merge, inadvertently deleted a branch, discarded a valuable local change, published a branch on a remote too soon, or simply committed something that you shouldn’t have – your lifesaver is as simple as “CMD+Z”, and it’s always available.

The Power of Interactive Rebase

Interactive Rebase is a wonderful example of Git’s more powerful features. It allows you to…

  • edit old commits (both their messages as well as their changesets!)
  • combine multiple commits into one
  • reorder commits
  • …and even delete commits you don’t need anymore!

All of these can help a lot to keep a code base clean and structured. But being a very powerful tool, Interactive Rebase is also an example of a Git feature that is quite complicated to use!

Many beginners are scared away by this complexity – meaning that, of course, they will miss out on the benefits of the feature! Many experienced developers know and use Interactive Rebase; but since the feature is hard to use, it costs them a lot of time and/or they make mistakes from time to time.

Knowing this, we had two goals for integrating Interactive Rebase into Tower: on the one hand, we wanted to reduce as much of its complexity as possible. And on the other hand, we wanted to make the feature easily accessible.

The result is that Interactive Rebase is now integrated right into the “Commit History” and accessible via simple drag & drop and menu options:

Countless developers that wouldn’t have used Interactive Rebase at all on the Command Line are now using it on a daily basis in Tower.

Quick Actions

Almost independent of its size, working in a code base means spending a lot of time searching: for example for a certain branch (out of lots of branches) or a certain file (out of lots of files).

Tower allows you to do most of your daily tasks without sifting through data – and even without taking your hands off the keyboard. The “Quick Action” dialog takes almost any input and allows you to simply act on it:

  • Give it a branch name and it will offer to do a checkout.
  • Give it a file name and it will offer to show it in the File History.
  • Give it a commit hash and it will offer to show that commit’s details.

Being able to perform many tasks right from the keyboard, without having to search and sift through loads of data, can make life as a developer a lot easier.

Solving Conflicts with Confidence

Everyone hates that moment when a merge (or rebase) stops and leaves you with a bunch of conflicts 😱 Multiple questions then quickly enter the poor programmer’s mind:

  • What are my changes? What are someone else’s?
  • What actually happened?
  • Why me?

While, admittedly, Tower cannot answer all of those questions, it offers a special “Conflict Wizard” that helps make the situation much more understandable:

You can easily see where changes came from and can solve the conflict simply by choosing which version to go with (or jump into a dedicated merge tool). It makes things a lot easier when merge conflicts are visualized in this way, instead of being an abstract mess.

Becoming More Productive

When building and improving Tower, we’re always striving to make things easier for our users – and to make them more productive. Let’s take some examples of where this shows in Tower:

  • No More Passwords, Tokens, SSH Keys: You can easily connect your GitHub / GitLab / Bitbucket / Azure DevOps accounts with Tower. Once connected, you never have to wrestle with passwords, authentication, tokens, usernames, and SSH Keys anymore. Cloning and interacting with a remote repository, then, is a matter of just a click.
  • Single-Line Staging & Discarding: There’s a golden rule in version control: when you make a commit, you should only include changes from a single topic (and not mix multiple topics in a commit, thereby making it extremely hard to understand what actually happened). With the ability to stage / unstage / discard individual chunks and lines from your changes, Tower enables you to create granular, precise commits.
  • A New Diff Viewer: In a very recent update, we gave our internal Diff Viewer a complete overhaul. It now highlights inline changes, allows you to show/hide whitespace changes, can display the complete file, and is completely customizable with themes!
  • Automations for the Boring Stuff: Tower takes care of much of the grunt work around Git. To take just two examples: neither do you have to regularly fetch new updates from the remote, nor do you have to remember to stage uncommitted changes before pulling/switching branches/merging… Tower does that automatically for you.

When I think about the favorite applications I use myself, they all have some things in common: they make life easier for me, they make me more productive, and they enable me to do advanced stuff that I’d have a hard time doing without them. I’m glad and grateful that many of our users consider Tower to be one of their favorite apps!

If you want to give Tower a try, just download it from our website and test it 30 days for free. And if you are a student or teacher, you can use Tower for free!


The post CMD+Z for Git is Here appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

The Smart Ways to Correct Mistakes in Git

The world of software development offers an infinite amount of ways to mess up: deleting the wrong things, coding into dead ends, littering commit messages with typos, are a mere few of the plentitude.
​​
​​Fortunately, however, we have a wonderful safety net under our feet in the form of Git when we’re working with version control. Not that you and I need a safety net, of course, because we never make mistakes, right? Sure, sure. But for the benefit of everyone else, let's take a tour of some of the "undo" tools in Git that can save us from ourselves.
​​
​​
​​
​​

Fixing the last commit

​​
​​Messing up a commit is all too easy. Classic case in point: making a typo in a commit message. Another? Forgetting to add a change to the staging area. And in many cases, we instantly realize our mistake — right after hitting the Enter key, naturally.
​​
​​Luckily, Git makes it ridiculously easy to fix the very last commit. Let's say we had just hit Enter on the following command:

​​

git commit -m "Massage full of typohs"

​​
​​And (as if this orthographic mess wasn't bad enough) let's say we also forgot to add another changed file to the staging area. We can correct both of our mistakes with the following two commands:
​​

git add forgotten-changes.js
​​git commit --amend -m "A sensible message"

​​
​​The magic ingredient is the --amend​ flag: when using it on a commit, Git will correct the very last commit — with any staged changes and the new message.
​​
​​A short word of warning, though: only use --amend​ on commits that haven't been pushed to a remote repository, yet. The reason is that Git replaces the original, bad commit with the amended version. Afterwards, it looks as if the original commit never happened. Yeah, that’s good for concealing mistakes, but only if we haven't already published this mistake on the remote server.
​​​​
​​

Undoing local changes

​​
​​Everyone’s had days like this: spend all morning hacking away, only to admit to yourself that the last few hours were a waste of time. Gotta start over and undo much (or all) of that work.
​​
​​But this is one of the reasons for using Git in the first place — to be able to try out things without the fear that we might break something.
​​
​​Let's take stock in an example situation:
​​

git status
​​  modified: about.html
​​  deleted:  imprint.html
​​  modified: index.html

​​
​​Now, let's assume that this is one of the wasted hacking days described above. We ought to have kept our hands off of about.html and not deleted imprint.html. What we now want is to discard our current changes in these files — while keeping the brilliant work done in index.html. ​​The git checkout​ command can help in this case. Instead, we’ve gotta get more specific with which files to check out, like this:

​​

git checkout HEAD about.html imprint.html

​​This command restores both about.html and imprint.html to their last committed states. Phew, we got away from a black eye!
​​
​​We could take this one step further and discard specific individual lines in a changed file instead of tossing out the entire thing! I’ll admit, it’s rather complicated to make it happen on the command line, but using a desktop Git client like Tower is a great way to go about it:

​​
​​For those really bad days, we might want to bring out the big guns in the form of:
​​
​​

git reset --hard HEAD

​​
​​While we only restored specific files with checkout​, this command resets our whole working copy. In other words, reset​ restores the complete project at its last committed state. ​​Similar to --amend​, there's something to keep in mind when using checkout​ and reset​: discarding local changes with these commands cannot be undone! They have never been committed to the repository, so it's only logical that they cannot be restored. Better be sure that you really want to get rid of them because there’s no turning back!
​​
​​

Undoing and reverting an older commit

​​
​​In many cases, we only realize a mistake much later, after it has long been committed to the repository.

​​How can we get rid of that one bad commit? Well, the answer is that we shouldn't… at least in most cases. Even when "undoing" things, Git normally doesn't actually delete data. It corrects it by adding new data. Let's see how this works using our "bad guy" example:
​​
​​

git revert 2b504bee

​​
​​By using git revert​ on that bad commit, we haven't deleted anything. Quite the contrary:

​​Git automatically created a new commit with changes that reverts the effects of the "bad" commit. So, really, if we started with three commits and were trying to correct the middle one, now we have four total commits, with a new one added that corrects the one we targeted with revert​.
​​​​
​​

Restoring a previous version of a project

​​
​​A different use case is when we want to restore a previous version of our project. Instead of simply undoing or reverting a specific revision somewhere in our commit history, we might really want to turn back time and return to a specific revision.
​​
​​In the following example scenario, we would declare all the commits that came after "C2" as unwanted. What we want is to return to the state of commit "C2" and forget everything that came after it in the process:

​​The command that's necessary is already (at least partly) familiar to you based on what we’ve already covered:
​​
​​

git reset --hard 2b504bee

​​
​​This tells git reset​ the SHA-1 hash of the commit we want to return to. Commits C3 and C4 then disappear from the project's history.
​​
​​If you're working in a Git client, like Tower, both git revert​ and git reset are available from the contextual menu of a commit item:

​​

​​Deleting commits, restoring deleted branches, dealing with conflicts, etc. etc. etc.

​​
​​Of course, there are many other ways to mess up things in a software project. But luckily, Git also offers many more tools for undoing the mess.
​​
​​Have a look at the "First Aid Kit for Git" project that I and other folks on the Tower team have created if you want to learn more about the scenarios we covered in this post, or about other topics, like how to move commits between branches, delete old commits, restore deleted branches or gracefully deal with merge conflicts. It’s a totally free guide that includes 17 videos and a handy cheat sheet you can download and keep next to your machine.

​​In the meantime, happy undoing!

The post The Smart Ways to Correct Mistakes in Git appeared first on CSS-Tricks.