Gi from the bottom up Wed,  Dec  by John Wiegley In my pursuit to understand Git, it’s been helpful for me to understand it from the bottom up — rather than look at it only in terms of its high-level commands. And since Git is so beautifully simple when viewed this way, I thought others might be interested to read what I’ve found, and perhaps avoid the pain I went through finding it. I used Git version 1.5.4.5 for each of the examples found in this document. Contents 1. License 2. Introduction 3. Repository: Directory content tracking Introducing the blob Blobs are stored in trees How trees are made e beauty of commits A commit by any other name… Branching and the power of rebase 4. e Index: Meet the middle man Taking the index farther 5. To reset, or not to reset Doing a mixed reset Doing a so reset Doing a hard reset 6. Last links in the chain: Stashing and the reflog 7. Conclusion 8. Further reading 2 3 5 6 7 8 10 12 15 20 22 24 24 24 25 27 30 31 http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley License is document is provided under the terms of the Creative Commons Attribution-Share Alike 3.0 United States License, which may be viewed at the following URL: http://creativecommons.org/licenses/by-sa/3.0/us/ In brief, you may use the contents of this document for any purpose, personal, commercial or otherwise, so long as attribution to the author is maintained. Likewise, the document may be modified, and derivative works and translations made available, so long as such modifications and derivations are offered to the public on equal terms as the original document. -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley Introduction Welcome to the world of Git. I hope this document will help to advance your understanding of this powerful content tracking system, and reveal a bit of the simplicity underlying it — however dizzying its array of options may seem from the outside. Before we dive in, there are a few terms which should be mentioned first, since they’ll appear repeatedly throughout this text: repository A repository is a collection of commits, each of which is an archive of what the project's working tree looked like at a past date, whether on your machine or someone else's. It also defines  (see below), which identifies the branch or commit the current working tree stemmed from. Lastly, it contains a set of branches and tags, to identify certain commits by name. the index Unlike other, similar tools you may have used, Git does not commit changes directly from the working tree into the repository. Instead, changes are first registered in something called the index. ink of it as a way of “confirming” your changes, one by one, before doing a commit (which records all your approved changes at once). Some find it helpful to call it instead as the “staging area”, instead of the index. working tree A working tree is any directory on your filesystem which has a repository associated with it (typically indicated by the presence of a sub-directory within it named .git.). It includes all the files and sub-directories in that directory. commit A commit is a snapshot of your working tree at some point in time. e state of  (see below) at the time your commit is made becomes that commit’s parent. is is what creates the notion of a “revision history”. branch A branch is just a name for a commit (and much more will be said about commits in a moment), also called a reference. It’s the parentage of a commit which defines its history, and thus the typical notion of a “branch of development”. tag A tag is also a name for a commit, similar to a branch, except that it always names the same commit, and can have its own description text. -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley master e mainline of development in most repositories is done on a branch called “master”. Although this is a typical default, it is in no way special.   is used by your repository to define what is currently checked out: • If you checkout a branch,  symbolically refers to that branch, indicating that the branch name should be updated aer the next commit operation. • If you checkout a specific commit,  refers to that commit only. is is referred to as a detached , and occurs, for example, if you check out a tag name. e usual flow of events is this: Aer creating a repository, your work is done in the working tree. Once your work reaches a significant point — the completion of a bug, the end of the working day, a moment when everything compiles — you add your changes successively to the index. Once the index contains everything you intend to commit, you record its content in the repository. Here’s a simple diagram that shows a typical project’s life-cycle: Earlier states of the working tree may be checked out from the repository at any time using git-checkout Working Tree Repository Changes are committed to the repository from the state of the index using Index Changes to the working tree are registered in the index using git-add git-commit With this basic picture in mind1, the following sections shall attempt to describe how each of these different entities is important to the operation of Git. 1. In reality, a checkout causes contents from the repository to be copied into the index, which is then written out to the working tree. But since the user never sees this usage of the index during a checkout operation, I felt it would make more sense not to depict it in the diagram. -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley Repository: Directory content tracking As mentioned above, what Git does is quite rudimentary: it maintains snapshots of a directory’s contents. Much of its internal design can be understood in terms of this basic task. e design of a Git repository in many ways mirrors the structure of a  filesystem: A filesystem begins with a root directory, which typically consists of other directories, most of which have leaf nodes, or files, that contain data. Meta-data about these files’ contents is stored both in the directory (the names), and in the i-nodes that reference the contents of those file (their size, type, permissions, etc). Each i-node has a unique number that identifies the contents of its related file. And while you may have many directory entries pointing to a particular i-node (i.e., hard-links), it’s the i-node which “owns” the contents stored on your filesystem. Internally, Git shares a strikingly similar structure, albeit with one or two key differences. First, it represents your file’s contents in blobs, which are also leaf nodes in something awfully close to a directory, called a tree. Just as an i-node is uniquely identified by a system-assigned number, a blob is named by computing the  hash id of its size and contents. For all intents and purposes this is just an arbitrary number, like an i-node, except that it has two additional properties: first, it verifies the blob’s contents will never change; and second, the same contents shall always be represented by the same blob, no matter where it appears: across commits, across repositories — even across the whole Internet. If multiple trees reference the same blob, this is just like hard-linking: the blob will not disappear from your repository as long as there is at least one link remaining to it. e difference between a Git blob and a filesystem’s file is that a blob stores no metadata about its content. All such information is kept in the tree that holds the blob. One tree may know those contents as a file named “foo” that was created in August 2004, while another tree may know the same contents as a file named “bar” that was created five years later. In a normal filesystem, two files with the same contents but with such different metadata would always be represented as two independent files. Why this difference? Mainly, it’s because a filesystem is designed to support files that change, whereas Git is not. e fact that data is immutable in the Git repository is what makes all of this work and so a different design was needed. And as it turns out, this design allows for much more compact storage, since all objects having identical content can be shared, no matter where they are. -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley Introducing the blob Now that the basic picture has been painted, let’s get into some practical examples. I’m going to start by creating a sample Git repository, and showing how Git works from the bottom up in that repository. Feel free to follow along as you read: $ mkdir sample; cd sample $ echo 'Hello, world!' > greeting Here I’ve created a new filesystem directory named “sample” which contains a file whose contents are prosaically predictable. I haven’t even created a repository yet, but already I can start using some of Git’s commands to understand what it’s going to do. First of all, I’d like to know which hash id Git is going to store my greeting text under: $ git hash-object greeting af5626b4a114abcb82d63db7c8082c3c4756e51b If you run this command on your system, you’ll get the same hash id. Even though we’re creating two different repositories (possibly a world apart, even) our greeting blob in those two repositories will have the same hash id. I could even pull commits from your repository into mine, and Git would realize that we’re tracking the same content — and so would only store one copy of it! Pretty cool. e next step is to initialize a new repository and commit the file into it. I’m going to do this all in one step right now, but then come back and do it again in stages so you can see what’s going on underneath: $ git init $ git add greeting $ git commit -m "Added my greeting" At this point our blob should be in the system exactly as we expected, using the hash id determined above. As a convenience, Git requires only as many digits of the hash id as are necessary to uniquely identify it within the repository. Usually just six or seven digits is enough: $ git cat-file -t af5626b blob $ git cat-file blob af5626b Hello, world! ere it is! I haven’t even looked at which commit holds it, or what tree it’s in, but based solely on the contents I was able to assume it’s there, and there it is. It will always have this same -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley identifier, no matter how long the repository lives or where the file within it is stored. ese particular contents are now verifiably preserved, forever. In this way, a Git blob represents the fundamental data unit in Git. Really, the whole system is about blob management. Blobs are stored in trees e contents of your files are stored in blobs, but those blobs are pretty featureless. ey have no name, no structure — they’re just “blobs”, aer all. In order for Git to represent the structure and naming of your files, it attaches blobs as leaf nodes within a tree. Now, I can’t discover which tree(s) a blob lives in just by looking at it, since it may have many, many owners. But I know it must live somewhere within the tree held by the commit I just made: $ git ls-tree HEAD 100644 blob af5626b4a114abcb82d63db7c8082c3c4756e51b greeting ere it is! is first commit added my greeting file to the repository. is commit contains one Git tree, which has a single leaf: the greeting content’s blob. Although I can look at the tree containing my blob by passing  to ls-tree, I haven’t yet seen the underlying tree object referenced by that commit. Here are a few other commands to highlight that difference and thus discover my tree: $ git rev-parse HEAD 588483b99a46342501d99e3f10630cfc1219ea32 # different on your system $ git cat-file -t HEAD commit $ git cat-file commit HEAD tree 0563f77d884e4f79ce95117e2d686d7d6e282887 author John Wiegley 1209512110 -0400 committer John Wiegley 1209512110 -0400 Added my greeting e first command decodes the  alias into the commit it references, the second verifies its type, while the third command shows the hash id of the tree held by that commit, as well as the other information stored in the commit object. e hash id for the commit is unique to my repository — because it includes my name and the date when I made the commit — but the hash id for the tree should be common between your example and mine, containing as it does the same blob under the same name. -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley Let’s verify that this is indeed the same tree object: $ git ls-tree 0563f77 100644 blob af5626b4a114abcb82d63db7c8082c3c4756e51b greeting ere you have it: my repository contains a single commit, which references a tree that holds a blob — the blob containing the contents I want to record. ere’s one more command I can run to verify that this is indeed the case: $ find .git/objects -type f | sort .git/objects/05/63f77d884e4f79ce95117e2d686d7d6e282887 .git/objects/58/8483b99a46342501d99e3f10630cfc1219ea32 .git/objects/af/5626b4a114abcb82d63db7c8082c3c4756e51b From this output I see that the whole of my repo contains three objects, each of whose hash id has appeared in the preceding examples. Let’s take one last look at the types of these objects, just to satisfy curiosity: $ git cat-file -t 588483b99a46342501d99e3f10630cfc1219ea32 commit $ git cat-file -t 0563f77d884e4f79ce95117e2d686d7d6e282887 tree $ git cat-file -t af5626b4a114abcb82d63db7c8082c3c4756e51b blob I could have used the show command at this point to view the concise contents of each of these objects, but I’ll leave that as an exercise to the reader. How trees are made Every commit holds a single tree, but how are trees made? We know that blobs are created by stuffing the contents of your files into blobs — and that trees own blobs — but we haven’t yet seen how the tree that holds the blob is made, or how that tree gets linked to its parent commit. Let’s start with a new sample repository again, but this time by doing things manually, so you can get a feeling for exactly what’s happening under the hood: $ rm -fr greeting .git $ echo 'Hello, world!' > greeting $ git init $ git add greeting It all starts when you first add a file to the index. For now, let’s just say that the index is what you use to initially create blobs out of files. When I added the file greeting, a change occurred -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley in my repository. I can’t see this change as a commit yet, but here is one way I can tell what happened: $ git log # this will fail, there are no commits! fatal: bad default revision 'HEAD' $ git ls-files --stage # list blob referenced by the index 100644 af5626b4a114abcb82d63db7c8082c3c4756e51b 0 greeting What’s this? I haven’t committed anything to the repository yet, but already an object has come into being. It has the same hash id I started this whole business with, so I know it represents the contents of my greeting file. I could use cat-file -t at this point on the hash id, and I’d see that it was a blob. It is, in fact, the same blob I got the first time I created this sample repository. e same file will always result in the same blob (just in case I haven’t stressed that enough). is blob isn’t referenced by a tree yet, nor are there any commits. At the moment it is only referenced from a file named .git/index, which references the blobs and trees that make up the current index. So now let’s make a tree in the repo for our blob to hang off of: $ git write-tree # record the contents of the index in a tree 0563f77d884e4f79ce95117e2d686d7d6e282887 is number should look familiar as well: a tree containing the same blobs (and sub-trees) will always have the same hash id. I don’t have a commit object yet, but now there is a tree object in that repository which holds the blob. e purpose of the low-level write-tree command is to take whatever the contents of the index are and tuck them into a new tree for the purpose of creating a commit. I can manually make a new commit object by using this tree directly, which is just what the commit-tree command does: $ echo "Initial commit" | git commit-tree 0563f77 5f1bc85745dcccce6121494fdd37658cb4ad441f e raw commit-tree command takes a tree’s hash id and makes a commit object to hold it. If I had wanted the commit to have a parent, I would have had to specify the parent commit’s hash id explicitly using the -p option. Also, note here that the hash id differs from what will appear on your system: is is because my commit object refers to both my name, and the date at which I created the commit, and these two details will always be different from yours. Our work is not done yet, though, since I haven’t registered the commit as the new head of the current branch: $ echo 5f1bc85745dcccce6121494fdd37658cb4ad441f > .git/refs/heads/master -- http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley is command tells Git that the branch name “master” should now refer to our recent commit. Another, much safer way to do this is by using the command update-ref: $ git update-ref refs/heads/master 5f1bc857 Aer creating master, we must associate our working tree with it. Normally this happens for you whenever you check out a branch: $ git symbolic-ref HEAD refs/heads/master is command associates  symbolically with the master branch. is is significant because any future commits from the working tree will now automatically update the value of refs/heads/master. It’s hard to believe it’s this simple, but yes, I can now use log to view my newly minted commit: $ git log commit 5f1bc85745dcccce6121494fdd37658cb4ad441f Author: John Wiegley Date: Mon Apr 14 11:14:58 2008 -0400 Initial commit A side note: if I hadn’t set refs/heads/master to point to the new commit, it would have been considered “unreachable”, since nothing currently refers to it nor is it the parent of a reachable commit. When this is the case, the commit object will at some point be removed from the repository, along with its tree and all its blobs. (is happens automatically by a command called gc, which you rarely need to use manually). By linking the commit to a name within refs/ heads, as we did above, it becomes a reachable commit, which ensures that it’s kept around from now on. The beauty of commits Some version control systems make “branches” into magical things, oen distinguishing them from the “main line” or “trunk”, while others discuss the concept as though it were very different from commits. But in Git there are no branches as separate entities: there are only blobs, trees and commits2. Since a commit can have one or more parents, and those commits can have parents, this is what allows a single commit to be treated like a branch: because it knows the whole history that led up to it. 2. Well, and tags, but these are just fancy references to commits and can be ignored for the time being. -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley You can examine all the top-level, referenced commits at any time using the command: branch $ git branch -v * master 5f1bc85 Initial commit Say it with me: A branch is nothing more than a named reference to a commit. In this way, branches and tags are identical, with the sole exception that tags can have their own descriptions, just like the commits they reference. Branches are just names, but tags are descriptive, well, “tags”. But the fact is, we don’t really need to use aliases at all. For example, if I wanted to, I could reference everything in the repository using only the hash ids of its commits. Here’s me being straight up loco and resetting the head of my working tree to a particular commit: $ git reset --hard 5f1bc85 e --hard option says to erase all changes currently in my working tree, whether they’ve been registered for a checkin or not (more will be said about this command later). A safer way to do the same thing is by using checkout: $ git checkout 5f1bc85 e difference here is that changed files in my working tree are preserved. If I pass the -f option to checkout, it acts the same in this case to reset --hard, except that checkout only ever changes the working tree, whereas reset --hard changes the current branch's  to reference the specified version of the tree. Another joy of the commit-based system is that you can rephrase even the most complicated version control terminology using a single vocabulary. For example, if a commit has multiple parents, it’s a “merge commit” — since it merged multiple commits into one. Or, if a commit has multiple children, it represents the ancestor of a “branch”, etc. But really there is no difference between these things to Git: to it, the world is simply a collection of commit objects, each of which holds a tree that references other trees and blobs, which store your data. Anything more complicated than this is simply a device of nomenclature. Here is a picture of how all these pieces fit together: -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley HEAD triangles are tree objects, held by commits and other trees circles are commit objects, which link to one or more parent commits — back to their original ancestor(s) — thus forming a "history" squares are blob objects, which are why the whole system exists every commit holds a tree, and every tree may contain any number of other trees and blobs in its leaves A commit by any other name… Understanding commits is the key to grokking Git. You’ll know you have reached the Zen plateau of branching wisdom when your mind contains only commit topologies, leaving behind the confusion of branches, tags, local and remote repositories, etc. Hopefully such understanding will not require lopping off your arm3 — although I can appreciate if you’ve considered it by now. If commits are the key, how you name commits is the doorway to mastery. ere are many, many ways to name commits, ranges of commits, and even some of the objects held by commits, which are accepted by most of the Git commands. Here’s a summary of some of the more basic usages: branchname 3. As has been said before, the name of any branch is simply an alias for the most recent commit on that “branch”. is is the same as using the word  whenever that branch is checked out. Cf. the example of the second Zen patriarch, Hui-k’o. -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley tagname A tag-name alias is identical to a branch alias in terms of naming a commit. e major difference between the two is that tag aliases never change, whereas branch aliases change each time a new commit is checked in to that branch. HEAD e currently checked out commit is always called . If you check out a specific commit — instead of a branch name — then  refers to that commit only and not to any branch. Note that this case is somewhat special, and is called “using a detached ” (I’m sure there’s a joke to be told here…). c82a22c39cbc32… A commit may always be referenced using its full, 40-character  hash id. Usually this happens during cut-and-pasting, since there are typically other, more convenient ways to refer to the same commit. c82a22c You only need use as many digits of a hash id as are needed for a unique reference within the repository. Most of the time, six or seven digits is enough. name^ e parent of any commit is referenced using the caret symbol. If a commit has more than one parent, the first is used. name^^ Carets may be applied successively. is alias refers to “the parent of the parent” of the given commit name. name^2 If a commit has multiple parents (such as a merge commit), you can refer to the nth parent using name^n. name~10 A commit’s nth ancestor may be referenced using a tilde (~) followed by the ordinal number. is type of usage is common with rebase -i, for example, to mean “show me a bunch of recent commits”. is is the same as name^^^^^^^^^^. name:path To reference a certain file within a commit’s content tree, specify that file’s name aer a colon. is is helpful with show, or to show the difference between two versions of a committed file: $ git diff HEAD^1:Makefile HEAD^2:Makefile name^{tree} You can reference just the tree held by a commit, rather than the commit itself. -  - http://www.newartisans.com///git-from-the-bottom-up.html name1..name2 © , John Wiegley is and the following aliases indicate commit ranges, which are supremely useful with commands like log for seeing what’s happened during a particular span of time. e syntax to the le refers to all the commits reachable from name2 back to, but not including, name1. If either name1 or name2 is omitted,  is used in its place. name1...name2 A “triple-dot” range is quite different from the two-dot version above. For commands like log, it refers to all the commits referenced by name1 or name2, but not by both. e result is then a list of all the unique commits in both branches. For commands like diff, the range expressed is between name2 and the common ancestor of name1 and name2. is differs from the log case in that changes introduced by name1 are not shown. master.. is usage is equivalent to “master..HEAD”. I’m adding it here, even though it’s been implied above, because I use this kind of alias constantly when reviewing changes made to the current branch. ..master is, too, is especially useful aer you’ve done a fetch and you want to see what changes have occurred since your last rebase or merge. --since="2 weeks ago" Refers to all commits since a certain date. --until=”1 week ago” Refers to all commits up to a certain date. --grep=pattern Refers to all commits whose commit message matches the regular expression pattern. --committer=pattern Refers to all commits whose committer matches the pattern. --author=pattern Refers to all commits whose author matches the pattern. e author of a commit is the one who created the changes it represents. For local development this is always the same as the committer, but when patches are being sent by e-mail, the author and the committer usually differ. -  - http://www.newartisans.com///git-from-the-bottom-up.html --no-merges © , John Wiegley Refers to all commits in the range that have only one parent — that is, it ignores all merge commits. Most of these options can be mixed-and-matched. Here is an example which shows the following log entries: changes made to the current branch (branched from master), by myself, within the last month, which contain the text “foo”: $ git log --grep='foo' --author='johnw' --since="1 month ago" master.. Branching and the power of rebase One of Git’s most capable commands for manipulating commits is the innocently-named rebase command. Basically, every branch you work from has one or more “base commits”: the commits that branch was born from. Take the following typical scenario, for example. Note that the arrows point back in time because each commit references its parent(s), but not its children. erefore, the D and Z commits represent the heads of their respective branches: A B C D W X Y Z In this case, running branch would show two “heads”: D and Z, with the common parent of both branches being A. e output of show-branch shows us just this information: $ git branch Z * D $ git show-branch ! [Z] Z * [D] D -* [D] D * [D^] C * [D~2] B + [Z] Z + [Z^] Y + [Z~2] X + [Z~3] W +* [D~3] A Reading this output takes a little getting used to, but essentially it’s no different from the diagram above. Here’s what it tells us: -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley • e branch we’re on experienced its first divergence at commit A (also known as commit D~3, and even Z~4 if you feel so inclined). e syntax commit^ is used to refer to the parent of a commit, while commit~3 refers to its third parent, or greatgrandparent. • Reading from bottom to top, the first column (the plus signs) shows a divergent branch named Z with four commits: W, X, Y and Z. • e second column (the asterisks) show the commits which happened on the current branch, namely three commits: B, C and D. • e top of the output, separated from the bottom by a dividing line, identifies the branches displayed, which column their commits are labelled by, and the character used for the labeling. e action we’d like to perform is to bring the working branch Z back up to speed with the main branch, D. In other words, we want to incorporate the work from B, C, and D into Z. In other version control systems this sort of thing can only be done using a “branch merge”. In fact, a branch merge can still be done in Git, using merge, and remains needful in the case where Z is a published branch and we don’t want to alter its commit history. Here are the commands to run: $ git checkout Z # switch to the Z branch $ git merge D # merge commits B, C and D into Z is is what the repository looks like aerward: A B C D W X Y Z Z' (Z+D) If we checked out the Z branch now, it would contain the contents of the previous Z (now referenceable as Z^), merged with the contents of D. (ough note: a real merge operation would have required resolving any conflicts between the states of D and Z). Although the new Z now contains the changes from D, it also includes a new commit to represent the merging of Z with D: the commit now shown as Z’. is commit doesn’t add anything new, but represents the work done to bring D and Z together. In a sense it’s a “meta-commit”, because its contents are related to work done solely in the repository, and not to new work done in the working tree. -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley ere is a way, however, to transplant the Z branch straight onto D, effectively moving it forward in time: by using the powerful rebase command. Here’s the graph we’re aiming for: A B C D W' X' Y' Z' is state of affairs most directly represents what we’d like done: for our local, development branch Z to be based on the latest work in the main branch D. at’s why the command is called “rebase”, because it changes the base commit of the branch it’s run from. If you run it repeatedly, you can carry forward a set of patches indefinitely, always staying up-to-date with the main branch, but without adding unnecessary merge commits to your development branch4. Here are the commands to run, compared to the merge operation performed above: $ git checkout Z # switch to the Z branch $ git rebase D # change Z’s base commit to point to D Why is this only for local branches? Because every time you rebase, you’re potentially changing every commit in the branch. Earlier, when W was based on A, it contained only the changes needed to transform A into W. Aer running rebase, however, W will be rewritten to contain the changes necessary to transform D into W’. Even the transformation from W to X is changed, because A+W+X is now D+W’+X’ — and so on. If this were a branch whose changes are seen by other people, and any of your downstream consumers had created their own local branches off of Z, their branches would now point to the old Z, not the new Z’. Generally, the following rule of thumb can be used: Use rebase if you have a local branch with no other branches that have branched off from it, and use merge for all other cases. merge is also useful when you’re ready to pull your local branch’s changes back into the main branch. Interactive rebasing When rebase was run above, it automatically rewrote all the commits from W to Z in order to rebase the Z branch onto the D commit (i.e., the head commit of the D branch). You can, however, take complete control over how this rewriting is done. If you supply the -i option to re4. Note that there are valid reasons for not doing this, and using merge instead. e choice depends on your situation. One downside to rebasing is that even if the rebased working tree compiles, there is no guarantee that the intermediate commits compile anymore since they were never compiled in their newly rebased state. If historical validity matters to you, prefer merging. -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley base, it will pop you into an editing buffer where you can choose what should be done for every commit in the local Z branch: pick is is the default behavior chosen for every commit in the branch if you don’t use interactive mode. It means that the commit in question should be applied to its (now rewritten) parent commit. For every commit that involves conflicts, the rebase command gives you an opportunity to resolve them. squash A squashed commit will have its contents “folded” into the contents of the commit preceding it. is can be done any number of times. If you took the example branch above and squashed all of its commits (except the first, which must be a pick in order to squash), you would end up with a new Z branch containing only one commit on top of D. Useful if you have changes spread over multiple commits, but you’d like the history rewritten to show them all as a single commit. edit If you mark a commit as edit, the rebasing process will stop at that commit and leave you at the shell with the current working tree set to reflect that commit. e index will have all the commit’s changes registered for inclusion when you run commit. You can thus make whatever changes you like: amend a change, undo a change, etc.; and aer committing, and running rebase --continue, the commit will be rewritten as if those changes had been made originally. (drop) If you remove a commit from the interactive rebase file, or if you comment it out, the commit will simply disappear as if it had never been checked in. Note that this can cause merge conflicts if any of the later commits in the branch depended on those changes. e power of this command is hard to appreciate at first, but it grants you virtually unlimited control over the shape of any branch. You can use it to: • Collapse multiple commits into single ones. • Re-order commits. • Remove incorrect changes you now regret. • Move the base of your branch onto any other commit in the repository. • Modify a single commit, to amend a change long aer the fact. -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley I recommend reading the man page for rebase at this point, as it contains several good examples how the true power of this beast may be unleashed. To give you one last taste of how potent a tool this is, consider the following scenario and what you’d do if one day you wanted to migrate the secondary branch L to become the new head of Z: A B C D W X Y Z I J K L e picture reads: we have our main-line of development, D, which three commits ago was branched to begin speculative development on Z. At some point in the middle of all this, back when C and X were the heads of their respective branches, we decided to begin another speculation which finally produced L. Now we’ve found that L’s code is good, but not quite good enough to merge back over to the main-line, so we decide to move those changes over to the development branch Z, making it look as though we’d done them all on one branch aer all. Oh, and while we’re at it, we want to edit J real quick to change the copyright date, since we forgot it was  when we made the change! Here are the commands needed to untangle this knot: $ git checkout L $ git rebase -i Z Aer resolving whatever conflicts emerge, I now have this repository: A B C D W X Y Z (B + C +) I' J' K' L' As you can see, when it comes to local development, rebasing gives you unlimited control over how your commits appear in the repository. -  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley The Index: Meet the middle man Between your data files, which are stored on the filesystem, and your Git blobs, which are stored in the repository, there stands a somewhat strange entity: the Git index. Part of what makes this beast hard to understand is that it’s got a rather unfortunate name. It’s an index in the sense that it refers to the set of newly created trees and blobs which you created by running add. ese new objects will soon get bound into a new tree for the purpose of committing to your repository — but until then, they are only referenced by the index. at means that if you unregister a change from the index with reset, you’ll end up with an orphaned blob that will get deleted at some point at the future. e index is really just a staging area for your next commit, and there’s a good reason why it exists: it supports a model of development that may be foreign to users of  or Subversion, but which is all too familiar to Darcs users: the ability to build up your next commit in stages. NEW HEAD HEAD The state of the index becomes the tree of the next commit Index Working tree Files, directories and "hunks" (individual changes within a file) are added to the index using git-add and git-add --patch First, let me say that there is a way to ignore the index almost entirely: by passing the -a flag to commit. Look at the way Subversion works, for example. When you type svn status, what you’ll see is a list of actions to be applied to your repository on the next call to svn commit. In a way, this “list of next actions” is a kind of informal index, determined by comparing the state of your working tree with the state of . If the file foo.c has been changed, on your next com-  - http://www.newartisans.com///git-from-the-bottom-up.html © , John Wiegley mit those changes will be saved. If an unknown file has a question mark next to it, it will be ignored; but a new file which has been added with svn add will get added to the repository. is is no different from what happens if you use commit -a: new, unknown files are ignored, but new files which have been added with add are added to the repository, as are any changes to existing files. is interaction is nearly identical with the Subversion way of doing things. e real difference is that in the Subversion case, your “list of next actions” is always determined by looking at the current working tree. In Git, the “list of next actions” is the contents of the index, which represents what will become the next state of , and that you can manipulate directly before executing commit. is gives you an extra layer of control over what’s going to happen, by allowing you to stage those changes in advance. If this isn’t clear yet, consider the following example: you have a trusty source file, foo.c, and you’ve made two sets of unrelated changes to it. What you’d like to do is to tease apart these changes into two different commits, each with its own description. Here’s how you’d do this in Subversion: $ svn diff foo.c > foo.patch $ vi foo.patch $ patch -p1 -R < foo.patch # remove the second set of changes $ svn commit -m "First commit message" $ patch -p1 < foo.patch # re-apply the remaining changes $ svn commit -m "Second commit message" Sounds like fun? Now repeat that many times over for a complex, dynamic set of changes. Here’s the Git version, making use of the index: $ git add --patch foo.c