Git for Humans
I first read a draft of Git for Humans before it was published, David Demaree asked me to read through and provide a quote for the site about the book. It’s a fantastic book and I reread it yesterday to highlight and be able to do a review justice.
I’ve been muddling my way through Git for several years now. I switched to the command line interface for Git in 2013 when I started working for Editorially, forcing myself to get more comfortable with how Git works. It isn’t easy to understand all the inner workings of Git, but Demaree makes it easier. After being in meetups and reading tutorials about how Git works and how branching works, I’ll be honest and say Demaree’s explanation is the first to make perfect sense to me. And Git itself, while difficult, is easiest to learn while using it.
I’m quite comfortable with Git now, branching, pushing, pulling, dealing with conflicts, but even so, I learned from this book. There are always little tricks that can be found in a book like this and Demaree does such a great job explaining them and helping you learn as you go. Even if you’ve worked with Git for a while, I still recommend this book, it was a great refresher for me. But if you are just starting to learn and use Git, then I really recommend this book. As with all the A Book Apart books, this is a great, quick read and packed with information.
My highlights are below broken up by chapter, since page numbers in an epub are so useless.
Git is a kind of model for present-day collaboration—that is, collaboration among distributed teams, working asynchronously, on a shared body of work.
Knowing when and how to commit a change is more than just a means of updating code—it’s also a practice for communicating and sharing work. It’s a process, and a remarkably powerful one.
Having spent most of the last decade using Git on almost every project, delving at times into some of the darkest, weirdest corners of Git behavior, I can safely say that it’s not you, it’s Git.
Believe it or not, Git’s challenging conceptual model is a feature, not a bug. Using Git feels like running with scissors because it’s a powerful tool that will let you bend time and space to your will, which sounds like—and is—a lot of responsibility to put in the hands of mere humans. But Git believes in your ability to handle such might, and so do I.
But there are at least two areas of our written culture where making incremental changes, and tracking those changes across multiple versions, is not just helpful but crucial: law and (more important for our story) software source code.
(The computing words “bugs” and “debugging” are popularly attributed to Grace Hopper, who traced problems in the operation of the Harvard Mark II computer to moths that had nested among the data relays.)
Eventually, though, I came to appreciate the benefits of having every significant version of my projects stored, annotated, and neatly organized in a secure location. It also helped me to think of commits as significant changes, as opposed to the hundreds of little changes I might save in a given hour. The extra steps involved in committing—the brief pause from coding, having to write a descriptive message, occasionally having to stop and address conflicts between my version and someone else’s—have ultimately helped me develop a more thoughtful and judicious way of working.
Fortunately, all of these things—enforcing rules, keeping track of versions in a repository of past work, shuttling changes back and forth between the repository and your working copy, even merging together two directories and policing conflicts—are things that computers can do a lot faster and better than we can. By learning and adopting an automated version control system like Git, we can keep our work neatly organized and our changes safely coordinated with one another, all without a lot of effort—that is, once we adopt and learn how to use such a system.
It’s more accurate to say that, rather than three different files, we’re talking about the same file in three different states. It’s the same file because even though its contents may change, its name stays the same; logically, therefore, it’s the same file.
…[W]e’re expected to know that behind each logical copy of a file in our working tree, Git is safely storing all the old versions of the file, in each of its previous states.
Git suffers from what I like to call an excess of simplicity.
Git’s design assumes that you not only know how version control systems work, but specifically how Git works.
Git is also one of the most matter-of-fact programs around. It never does more than you tell it to do (though it can be easy to accidentally tell Git to do more than you wanted it to do).
If you do get into Git’s version of trouble—like if Git can’t easily reconcile conflicting changes, or if it’s uncertain about where to commit your work—there is always a command that will get you out of it, often with whatever work you were trying to save still intact.
Typing commands and seeing the responses Git gives back is a great way to learn about how Git actually works, which will pay off when you inevitably run into a confusing situation down the road.
Git is more concerned with managing your commits than with the files whose changes you’re using commits to track. It’s not exactly that Git is indifferent to the contents of your files; it’s just that its model for organizing and managing your work is oriented around commits.
Git is a system of accumulation. It accumulates every change you tell it about, so that you can go back and explore that history later on.
Every commit is self-contained: it doesn’t just reference the things that have changed; it references everything that makes up the state of your project at a given moment.
Frankly, most of the time it’s not only acceptable to use –all, but you’re also almost always better off doing so. Not only will it save you time, but fewer commands means fewer opportunities to accidentally give Git a wrong signal, leading to confusion and heartbreak. With this option, you’re telling Git to trust that the version of your project in the working copy is an accurate reflection of what you’d like to commit.
Branches allow us to manage and work with other kinds of versions in Git—experiments, alternate takes, scratch pads—separately from the “official” copy of the work represented by the master branch.
The best way to understand branches is on their own terms, as a way of organizing and describing work. And the best way to explain that is for us to dive in and start growing some branches.
There’s no harm in treating branch names like folders in the metaphorical Trapper Keeper of your project.
Ultimately, a branch’s most important role is as a signpost or bookmark, pointing you back to a particular version of your work, distinguishing master from, say, another branch named
…[B]ranching is quick and cheap, and you’re under no obligation to reconcile the version of your work in a branch with the one in
…[W]hile merge conflicts are more annoying than truly scary, you’re better off avoiding them whenever possible by merging in master regularly.
The goal of keeping master and topic branches up to date with each other isn’t to prevent conflicts, but rather to make conflicts easier to manage by keeping the differences between two branches small.
There’s an old saying (which, like the word “bug,” is popularly attributed to Grace Hopper): A ship in port is safe, but that’s not what ships were built for.
Remotes are one of Git’s most successful abstractions. Unlike branches, which are wholly virtual copies of your project, each remote corresponds to an actual, physical copy of your repository with which you can exchange data.
In theory, Git doesn’t consider any one repository to be the canonical one for a given project, although in practice most teams have a single shared remote copy (often hosted on GitHub) that they consider the primary one—what Git conventionally calls the origin.
The hub also serves as a reliable backup of the code….
Although the hub is the most canonical backup copy of your repo, every copy contains the complete history of your project.
With remotes, just as with branches, you’re still managing different versions. In fact, your interactions with remotes will almost always be in the context of a branch.
The simplest way to set up a tracking relationship is to include the
-u) option when invoking
The information you put into a message, therefore, should be valuable and useful to the people who will read it.
Finding simpler ways to describe something doesn’t just make the changes you’ve made more comprehensible to your teammates; it’s also a great way to save space.
Each commit represents not only a snapshot of our whole project, but (except for the first one, of course) also a change from a previous commit. Eventually, once you start thinking and working in versions, you will want or need to compare the versions to see, specifically, what has changed. A commit message can give you a summary, but Git also offers a handy way to actually inspect the differences between two commits.
But I also find that looking at these things as systems misses Git’s most wonderful quality: people like you (and me, and our teammates) each making changes, evolving our projects one step at a time, crafting histories.