Git concepts simplified
gitolite.com
gitolite.com
> Hg folks should read this section carefully. Among various crazy notions Hg has is one that encodes the branch name within the commit object in some way. Unfortunately, Hg's vaunted "ease of use" (a.k.a "we support Windows better than git", which in an ideal world would be a negative, but in this world sadly it is not) has caused enormous takeup, and dozens of otherwise excellent developers have been brain-washed into thinking that is the only/right way.
If this is one of the most important concepts, starting it off with a slew of negativity about things the reader may be currently using (windows, hg) is probably not the best way to get them to keep reading.
Linus mentioned that having a "generation" in a commit, which is 0 for the empty commit and 1+max(generation of ancestors), would have sped up some merge operations.
Finding the history of a single file requires traversing the entire commit tree.
But there is no "fundamental" problem or bad design choice - and in fact, this data can easily be cached without adding it into the protocol.
EDIT: saw some complaints listed in this thread - they all have to do with UI, not with a design choice that is limiting use. Wrappers like "eg" and "legit", and macros, can be used to fix the UI (except no one agrees on what a better UI looks like).
For many people, UI issues carry way more weight as "design choices that limit use" than issues such as speed, efficient disk usage, data normalization, etc.
"Wrappers like "eg" and "legit", and macros, can be used to fix the UI"
I disagree. There may be some that _could_ be used, if 'the internet' wasn't filled with helpful comments using the 'real' UI. As it stands now, there is no replacement that has sufficient market share to give users a good chance of finding questions to answers they may have. It is a bit like the early days of Windows, where "how do I..." Questions got answered by "that's easy. Exit to DOS..." (Linux on the desktop has a bit of a similar problem, but I think it is slowly outgrowing it)
"except no one agrees on what a better UI looks like".
That may be an indication that it really is hard (maybe even impossible) to find a good alternative UI hat does not leave out git features.
As the most simple example, git add both adds a new file to the index and adds changes to an existing file to the index. They are both fundamentally the same operation, but your intentions are different.
Checkout, likewise, both changes HEAD's symbolic reference and changes the working copy to match the index or a commit. This makes it both a means of full tree changes and local reversion.
And then there's rebase...
I don't think these are huge issues, but I do think they're barriers and a source of common early issues with git for new users.
[1] I assume this was originally done to make git seem more approachable to SVN or CVS users.
The branch switching behaviour definitely seems taken from cvs, but somehow works much much better than it ever did in cvs.
git remote -v
git branch -a
git tag -l
git stash list
1 concept, 4 commands. I don't even use git much but this stuff makes it really slow to pick up."git stash" is certainly the outlier; the reason is that when "git stash" was first added it was meant to be a short-and-sweet command. Otherwise, you'd be forced to type "git stash save <name>", which is not so short and sweet. "git stash list" was added later.
For the latter two, no disagreement there. For the former two, there is a difference between returning a verbose response within the context queried and expanding the inclusive parameters of the query. 'git remote -a' makes no sense as all remotes are equivalent (there is no such thing as a local remote).
git push origin :topic-branch
can now be replaced with git push origin --delete topic-branch
This still has issues, there's now multiple ways to do the same thing. There's also different commands (git push vs git branch) for the same action, deleting a branch (regardless if it is remote or local).Want to checkout a branch?
git checkout <name>
Want to make a branch? git checkout -b <name>
Want to reset a file? git checkout -- <file_name>
Want to make a sandwich? git checkout ++D@A#32
Want to rebuild universe from Big Bang? git checkout 0 --rebuild-universe
Add to this, duplicated commands, arcane commands (hello fsck --lost-found), commands that seem deceptively similar but aren't, doesn't work out of the box like hg, doesn't work on Windows etc.I love git, but its CLI is horribad. And that is after most of weird/confusing stuff was removed...
Git, like Linux is a power tool. It's like those drills that can drill through solid rock easy, but if they jam, they'll spin you around.
When authors try to explain how to use git, they often have a very particular workflow in mind and don't necessarily describe exactly what that workflow is. This can cause major problems when somebody tries to apply advice to their own workflow.
I like the graphical approach. It helps to conceptualize what is going on so that someone can apply it to their own situation.
If you're using svn you need to understand the implementation to get why copies are cheap, so that you can understand how to use branching and tagging appropriately.
Git seems to force you to dive straight into the deep-end, since everything is a branch, even your own local working folder.
Since I failed to understand SVN after years of use I would say it is more complex.
There's no symmetry in commands. The opposite of "git commit" is "git reset --soft HEAD^", not "git uncommit". "git reset" is three commands in one. I could go on and on.
git was designed as a data model and then a series of slapdash commands that enabled manipulation of that data model. It wasn't designed from the end-user perspective backwards, and this really, really shows.
I much prefer bzr's porcelain, it's much better designed and more sane.
If you make a specific case for 'git reset --hard/--soft HEAD^' then you would either be left without the more general (and more useful) 'git reset --hard/--soft <commit>', or you would have a situation where you have to use 'uncommit' to (for example) re-commit something, or you would be left with a git-uncommit command and still have git-reset for related operations. Why would any of that be better?
You could split the third invocation of git-reset off into its own command, but honestly I don't see the utility in doing that.
git reset --hard tells you absolutely nothing obvious about what it's going to do unless you understand git and its specific incantations. You have to learn git commands, rather than intuit them.
-p, --patch
Interactively choose hunks of patch between the index and the work
tree and add them to the index. This gives the user a chance to
review the difference before adding modified contents to the index.
This effectively runs add --interactive, but bypasses the initial
command menu and directly jumps to the patch subcommand. See
“Interactive mode” for details.I actually use the GIT-SVN bridge to work with git locally, pushing my changes up to SVN when I've resolved a topic. I do this in part because of the staging area. I have accidentally included changes in SVN commits so many times that I try to avoid SVN altogether.
I agree though, so many of the commands are just plain painful, the reset command(s) is a perfect example.
You have it right that it was designed as a data model. Linus has said a few times that it wasn't originally intended to be the Source Control Management tool itself, but more like a kit for building an SCM. Sadly, it took off so quickly simply because Linus built it and was adopted as the end user solution.
I'm curious, as someone who actually loves it, what if there were just better tools for incrementally altering the last commit instead? I'm not sure that a more robust git commit --amend couldn't achieve the same goals as the index with less conceptual overhead.
git is more complex on the surface, but I really find it to be so much simpler when you end up in real nontrivial use cases, especially when something goes wrong. I would agree that the commands are sometimes too memorization-intensive, but I've never been in a situation with git, even as a beginner, that I couldn't figure out and resolve relatively easily, especially with all the online resources available. I can't say the same for svn. Maybe I was just an idiot when using svn, but if I was an idiot there, I don't see why I wouldn't be an idiot with git as well unless there was just something fundamentally more useable and flexible and understandable about git.
When I tried the same approach with mercurial I found that rewriting the history is not reasonably supported by mercurial. They use these append-only data structures that sound nice, because they assure stability (no already written data is ever in danger because files are only appended) and fit with the general concept of commit->pull->merge. But when you want to change the commit history you clash with the append-only concept. The Histedit extension which is meant to provide this feature has warnings all over the place that what I do is dangerous and might result in data loss with changeset backups written to locations in the working tree. It's horrible compared to Git.
Now don't misunderstand me, Git has a lot of problems UI wise (for example I still don't really grasp what is going on with detached heads). But I find the fundamental design choices more sound than with any other VCS that I have tried.
> Histedit extension which is meant to provide this feature has warnings all over the place
Ignore them — you do know, what are you doing, right?
> changeset backups written to locations in the working tree
That's not true. Backups are written to .hg/strip-backup directory, which isn't tracked.
No, I don't :). I heavily rely on my CVS to never loose any data and to be able to go back to a previous state whenever I need to. This works great with Git's reflog.
> That's not true. Backups are written to .hg/strip-backup directory, which isn't tracked.
I stand corrected then. Maybe I'm mixing that up with amend or revert? I last used mercurial 9 months ago, but believe to remember there was some command that left .orig files lying around and that if you applied a history rewrite command several times those backups were overwritten with the new backups and you weren't able to come back all the way.
as I've said, old commits are backed up.
> This works great with Git's reflog.
except git reflog is cleaned on git gc
Not mentioning http://mercurial.selenic.com/wiki/ChangesetEvolution feature is being in development.
> there was some command that left .orig files lying around
Only way .orig files may pop up is failure to replay rebased commit(s). No way these are backups — their purpose is to make user able to fix things and continue.
Unix.
You are operating primarily on a tree of files and streams of text. To operate on these you have a wide array of utilities that perform simple tasks (and a handful that perform complex tasks as well) that, when composed, allow you to perform any transformation you want. You can get a freshman CS student off the ground with Unix like systems in what, one lecture?
Because the (bleedingly simple) data model is the focus when learning Unix, you don't need to memorize every single little edgecase of the system. Knowledge of the data model alone is enough to tell you what sort of things can or cannot be done, and general knowledge of the sort of thing that a few utilities do is enough bootstrap yourself. If you want to list out some files in some particular way, you may not know immediately what exactly to type, but you probably do know that ls or find is a decent place to start looking.
Why go with boring open, creat, read, write when you can have rerere and prune and annex and reflog?
Do you really think that 'prune' is a worse name than, say, 'fcntl'?
(Also, git-annex is a separate project from git.)
I'm talking about the botched merges, the hour-long rebases with 156 git rebase --skip, the "your branches have diverged" mysteries, the subtle differences between fetch and pull, the fact that all the GUI I've seen so far, far from being a tool, are actually complicating the task with their own little syntax.
If the underlying data structure is so beautiful, then how come there's no UI where I can simply drag branches around, have an actual "OOPS, MISTAKE, LET ME UNDO" button, reorder my commits with the mouse, something that holds my hand and actually cares about ALL git users, not the 1% connoisseur elite?
Don't get me wrong, I have zero doubt that git is in fact an absolutely great tool with a very intelligent design and that the users are to blame for not understanding it, I'm just thinking that if after more than 5 years of git-as-a-dominant-dvcs I keep seeing the same puzzled faces looking at a series of SHA-1 like it's the answer to the universe over and over again, there's something that's not quite right.
That being said, this is indeed one of the most comprehensive articles I've ever read about git.
While I agree with some of your criticisms, as the resident 'Git guy', I've found no difficulty in explaining to people that pull is a convenience alias for 'fetch followed by merge'.
None of that implies that "git is [an] unholy clusterfuck of a product," it means that git is complicated. For 99% of the work you do with git, it isn't even that complicated and you don't need to be aware of the data structure. As for the last 1%, well, that's what separates git from other (D)VCSs. Git gives me the power to do a lot, and incidentally, it gives me the power to shoot my foot off too...I still prefer it over say svn or hg, my personal opinion.
Git is clever, moderately novel and therefore unfamiliar (depending on your background), and simple. There are not many concepts present, and the concepts that are there are not difficult to understand, but those concepts need brief introduction because they are concepts that many will be unfamiliar with.
If you buy a checkers board it will come with a (very simple) rulebook. You aren't born with some sort of natural checkers ability, you have to learn it. Nobody would claim that checkers isn't simple though.
Git may well be similar: relatively simple rules, yet hard to use proficiently.
Simple rules/components, complex gameplay/capability.
If you want to play it simple, git does that too.
You haven't met some of the developers on my team.
-You have a directory/folder (data store) where all of the files are stored
-Some files (blobs) are data files
-Some files (trees) are hierarchy - they specify the structure of the data (blob) files, and how they connect to each other (like a database)
-Some files (commits) are snapshots (think VM snapshot) of the arrangement of the blobs on the trees (data file structure)
-Some files (tags) hold metadata about the data files (blobs)
And it's all tied together by SHAs (GUIDs) which are just a random number that's so huge it's probably unique.
You can use URLs to point to any of these files, so you can tie together files from different locations at once.
I created a screencast about it @ http://sysadmincasts.com/episodes/11-internal-git-server-wit...
Gitlab is essentially an open-source clone of Github's web UI. Of the two projects, I think Gitlab is harder to deploy and far more resource-intensive on the server, but easier for users.
The index is a staging area for your commits. When you use `git add`, changes in the working directory are staged (prepared) for the next commit. If you pass the `-a` flag to `git commit`, Git will stage all changes to files that it is already aware of. (Recall that new files are untracked and must be manually added to the index the first time they're committed; `-a` won't add those files because Git doesn't already know about them.)
Why have a staging area instead of just creating a commit directly from all the changes in the working directory? It's basically a sanity measure for organizing commits if you're ever anything less than a perfect developer. If you make a bunch of changes and later realize that there's more than one "unit of work" represented in those changes (however you choose to define those units), you can selectively add files to the index to create commits that make sense. You can even use the interactive mode of `git add` to selectively stage changed sections within a single file. If you care about the benefits of sensible commits -- bug hunting with bisection, ability to run `git revert` to undo a logical unit of work -- then the index is your friend.
A few random pages on the index that I pulled up:
[0] http://www.gitguys.com/topics/whats-the-deal-with-the-git-in...
[1] http://git-scm.com/book/en/Git-Tools-Interactive-Staging
But I hold Linus in high enough regard to take very seriously the possibility that the index is a reflection of some deep wisdom that I have missed. That's the real reason I raise this every now and again.
Are you implying git man pages are useless to newcomer?
I think that Google is probably the best way to go, because not only will you get the man pages as high ranking hits, but you'll also get great hits from sites like StackOverflow and blogs that can explain things better.
"At times"? Well, maybe, if you have plenty of time, no paid work to do and no deadline approaching.
I think git!=simple but I applaud the effort.
Mercurial on the other hand has a pain in the ass datamodel (so much so that most introductions to it that I have seen do not even approach the topic), so you actually do have to learn all of the UI commands to get an idea of what can be done and what cannot be done. It is far more complex than git.
I really cannot think of a simpler VCS than git. I've used plenty, but never got off the ground faster with anything else.
Conceptually speaking, a centralized VCS -- one where you check out files, make changes, and check files back in when you're done -- is vastly simpler than Git. Sure, it's also much less powerful (and I would choose Git a million times over such a system), but it's definitely simpler.
I love Git's plumbing; I just hate its porcelain.
Meanwhile git through eclipse causes nothing but trouble as far as I have seen. Making it seem like SVN is exactly the problem, git isn't like SVN so if it seems that way, something is going wrong. Pretending git is something that it isn't will bite you in the ass sooner rather than later. The disappointing part is that there isn't any technical reason why git integration in eclipse couldn't be good, it just isn't currently.
The problem is with that particular tooling. That tooling presents a workflow that is perfectly fine (though it is problematic that it does not facilitate alternative workflows, which becomes particularly problematic when working on a team with other users), but it obscures what is actually going on and executes that workflow imperfectly, generally falling over in rather novel ways. When it fucks something up, and it eventually will, you will need an understanding of the basic concepts underlying git to figure out what went wrong. I'm not saying you need to know how to use the default git porcelain, I'm saying you need to be aware of the concepts underlying git.
The reason why most introductions to mercurial do not mention its data model is because it is _not_ important. You really do not need to care about it at all on your day to day use. I've been using mercurial for years and I've never had to ask myself what is mercurial's data model.
IMHO the reason why git forces you to understand its data model is because its UI is terrible. It is a failure of the tool when you need to understand how it works internally to use it. If git's UI were better its awesome data model would be something that only git devs would need to understand.
(i use mercurial when i start new projects, not because its fundamentally better - but because it has sensible defaults and i don't need to configure it our get bitten by gotcha x for the nth time - e.g. i can revert a merge without reading a document or configuring anything - a vital feature of source control imo)
I commit my changes to my own repo and keep building changes under HEAD, committing as I go. If I get a branch from a buddy and I want to add it to my code, I either merge or rebase depending how I want his commits to be intertwingled.
Because GIT is decentralized, there's no difference between merging in a buddy's branch, and import the latest changes from Origin into my branch. So I fetch the changes from origin/master and then rebase or merge my repo on top of that. That's my "Get Latest" command, basically, right? Assuming I'm working on "master", I fetch then rebase or merge origin/master.
To check in, I tell the origin server to take my stuff and then rebase or merge its master with that.
I still feel like this is a rather baroque approach to the problem... managing oodles of local commits separate from rebase/merges seems bizarre, above and beyond the decentralized approach that makes my own repo, my peer, and the "origin's" stuff all equivalent.
The decision of when to merge vs. rebase is still confusing to me.
i always thought it was "never rebase commits you have pushed" at least you know about rebase though. :)
Before you push, you can choose to do it either way.
Regarding merge vs. rebase, here's my approach: rebase to keep history a straight line when it's just your changes and it's just a few commits. If it's too many commits you tend to have more conflicts and it's usually easier to merge.
But then what happens when I merge/rebase locally and publish at the same time as somebody else? I assume the origin doesn't keep a lock on the whole mess while I'm doing my local merge/rebase. That sounds like it would lead to a "last-one-wins" conflict-resolution or a complete reversion of the origin if I publish without rebasing on the origin's version first.
If we're recommending books on Git, I recommend Pro Git. It's free online. http://git-scm.com/book
So yes, if you try to push without pulling down changes, then you will get an error about the histories having diverged. Sometimes you don't care about wiping out history, and can just push with the -f parameter, but most of the time that's your cue to rebase.
Branches are cheap in git, and there's no real advantage to doing dev work in the master branch. You should create a branch at least as often as you develop a new feature. In git, history isn't necessarily a static thing. Sometimes you need to mangle it to achieve your goals, and sometimes you fat-finger the merge and do time in History Hell. In either case it's only a huge flaming deal if you're working on master. Ditto with the problem you describe.
I recommend the O'Reilly book on Git. The simple explanations are nice in theory, but this is a complicated subject and deserves a full explanation. There are very sound reasons for the how and why of git, and they should be within the grasp of any aspiring programmer.
Branches are just pointers to commits. When you make a new commit on a branch, the new commit is saved and the branch pointer is moved forward to point to the new commit.
Tags point to branches, but they don't get updated. They're supposed to be permanent (but can be modified if you try). If a tag points at a commit and you make a new commit, the tag still points to the original commit.
The only other odd term would be HEAD, which is like a branch that ALWAYS points at what you have checked out at the moment. You'll see this if you make commits without being on a branch (say you checked out an old commit and just started working).
Since branches are just pointers at commits, you can move them around easily. If you make 6 new branches, they all just point at the same commit to start, which is why it's so amazingly fast to do. If you want to undo a commit (that you haven't pushed), you can move the branch pointer to a previous commit. After that when you make new commits it will be like the mistake never existed. (Note: If you accidentally do this, it can be fixed if you catch it soon enough).
When it comes to using branches, "A Successful Git Branching Model" [1] is very commonly used, and works fantastically. I had almost no trouble getting the other developers in my company on the model, and it makes it very easy to keep things straight.
If you'd like help understanding Git, I'd be glad to try to help you. My email address should be in my profile. You may find this kind of thing much simpler if you look at the graph of repository. My company uses SourceTree[2], which is a pretty great GUI and makes it easy for me to see how the various branches I've got relate.
[1] http://nvie.com/git-model/ [2] http://www.sourcetreeapp.com (Mac & Windows, Git has a basic gui command built in if you want)
Did you mean to say tags point to a commit?
You're right that the remote server has no idea what you're doing on your local machine.
> . That sounds like it would lead to a "last-one-wins" conflict-resolution or a complete reversion of the origin if I publish without rebasing on the origin's version first.
Unless you use a --force option, Git will refuse to do anything that will lose information. When the second person pushes, Git will say "You can't get here (new commit) from there (their commit). Nope." In that case it's up to you to pull their changes and either merge or rebase your changes. At that point when you push your new commit will be a descendent of the server's latest copy, and it willy happily accept it.
Now Git's messages aren't that friendly. The message you'll get is something like 'Unable to perform a fast-foward merge'.
I'll take a stab at this. When you merge, you're merging their branch into yours. At the end of you have both pieces of code, but all the history is still there. You can see that their branch started 20 commits ago, that two things were done in parallel, and that they came back together with the merge.
Rebase is a different. Rebase changes history. Rebase is like this:
1. Find the common ancestor between the branches
2. Extract each commit from their branch, turn it into a patch (named patch*N*)
3. Switch back to your branch
4. Apply patch1, patch2, ... patch*N* in order
Now it looks like all the work was done at the end of your brach, like it was based on your latest commit. Instead of being based on the common ancestor commit, it is now based on your commit. It's been rebased.It makes sense if you think about Git coming from kernel development. Developers tended to work in patch sets, a long series of patches to a common base. Rebaseing is just applying that series of patches to a different base.
Merging merges branches, but rebase is more like moving branches.
“There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.” ― C.A.R. Hoare
My favorite thing about Git is how it's forced Perforce to add features and lower price. Thanks Git!
Say you have a project which is a hundred megabytes big. And you have to develop almost in parallel three or four "generations" of the project -- let's say. v1, v2 and v3. In parallel means you'd like to be able to build any of the three versions without having to take the version out of the repository first. You can't say that v1 is obsolete, as soon as some bugs are reported in v1 you have to fix them in v1, v2 and v3. And every bigger version is "newer" but some features can be added in v2 and v3 some just in v3 etc.
How can you work on such a big project and have a single repository where all three versions are present, and work on these three versions in parallel (having sources which are compiled in different base directories)?
With my nascent Git understanding, I think you would just have multiple branches for v1, v2... and then clone the repository multiple times so you have multiple working copies.
Check out v1 in the first one, v2 in the second one.
Although changing between related branches is usually quite quick in Git. Also, a fresh checkout of ~100mb is not a lot. At least for an SSD.
This also relies on having a centralised Git repository for you to push/pull changes to. But I believe Git allows you to synchronise multiple repositories on disk.
You're rarely developing two things at once in any given instant of time... why not just quickly check out the branch you want?
This is probably not what you want. First, you should know that switching between branches in Git is insanely fast. In general, it won't get in your way.
If you clone the repository, each one is a full git repository. That means you'll triple the storage on the disk. Worse, you'll have to do 3x as many pulls to keep all 3 repositories up to date.
> You're rarely developing two things at once in any given instant of time... why not just quickly check out the branch you want?
It often comes up, but that's what we do. We may have a dozen branches on our machines (the thing(s) we're working on, recent things we worked on, the one that's been sitting for a while we're waiting on an answer to pick up again) and we can switch our project within a second or two on a simple rotating hard drive.
You can do a "git checkout" to get a copy out. On a modern drive, checking out a hundred meg history takes a few seconds.
You do not need multiple copies on the disk at the same time - "git checkout v1" when you are working on v2 will do only the changes necessary to make your directory into "v1", and then you can do "git checkout v2" or "git checkout v3" to get another version.
Alternatively, you can just mount your git repo as a filesystem, e.g. https://github.com/davesque/gitfuse (there are other projects - this came up on search, never used it myself).
And anecdotally, my git repos with tens of branches tend to take much less space than one checkout. git is super efficient about storage.
Note that 100MB is not really big in Git; you don't really run into issues until you are in the low GB range (at which point you should probably be considering if perhaps you actually have multiple different projects in the same repository. If so, that is where tools like 'repo' can step in: http://en.wikipedia.org/wiki/Repo_%28script%29)
I know I've seen guides online of how to deal with the exact problem you're describing, but I can't remember where to find any of them right now.
http://pragprog.com/screencasts/v-jwsceasy/source-control-ma...
Once you understand you're not going to pick it all up in an afternoon (just like a language) and that there will be lots more to learn down the road (like a language), git feels great.
If you are happy with whatever tool you are using, why change?
Debuggers aren't much harder than pour and drink. Dependency managers are pain in the ass (unsolved problem in CS) but you don't wrestle with them every day. I don't use very many features of my Eclipse and I don't use terribly many commands in vim. I also use arrows, I kid you not.
The problem only manifests when you're trying to do things like "show me only commits from branch <X>". Or, "show me when branch <X> was created from master."
git init
git add .
git commit ":-)"
git push origin master
6 months later I still look it up... this tutorial is for me!also if you think x pages of anything is a simple explanation then you missed a trick or two.
e.g if you have to explain why your arrows are pointing backwards you are doing it wrong, instead of using the standard notation for graphs and lists and stuff which are not generally well known, use what most people will understand on inspection.
'Repo' means 'reciprocity' or 'reposotory' if you didn't know.
I also wanted to post this screenshot and then saw your comments...