S Fox wrote:the git simulator allows me to detach the head from the branch and i am able to do commits while the head is attached directly to a commit node without any branch tag on it. is that not supposed to be possible in the real world?
I'm sorry, that was my mistake. Yes, you ARE allowed to create commits in a detached
HEAD mode. The problem is that Git won't track which commit you were at when you switch back to one of your other branches, and unless you have a good memory or write down the commit hash, you won't easily find back the work you did while in a detached
HEAD state. So making commits in a detached
HEAD state is useful when you want to experiment a little and then 'throw away' your work when you switch back to a real branch. It's not advisable to perform experiments this way though. Because branches are really only pointers to a commit, they are very cheap, so you might as well make an experimental branch instead of using a detached
HEAD. You never know what code you write that you might end up wanting to save. In practice, detached
HEAD is only used to check out an old version of the code so you can look around in it quickly, or run it to debug it or see how it works (for instance, if one of your customers is running an older version).
also from my experimenting on that site, it seems that doing "branch -f" is exactly the same thing as doing a rebase ?
No. In a rebase, you take the changes of the current working branch relative to the common ancestor with your target branch, and replay the changes on top of the target branch. The current branch is moved to the latest rebased commit (which now has the target branch as one of its ancestors), while the target branch is unaffected. Remember: Git commands normally make changes in the currently checked out branch, not target branches. When you perform "
git branch targetBranch -f" on an existing branch named "
targetBranch", the only thing you do is move the "
targetBranch" sticky note to the same commit as your currently checked out commit. No changes are made to your current branch, and any commits that were in the target branch that weren't part of any other branch, are now 'lost'.
as long as all the commits are on the branch, the order that the commits are in should not matter at all right?
Yes and no. It doesn't matter in terms of what the resulting code looks like. Regardless of the order, the code will be the same and running the application will yield the same results. It does matter in terms of history: Application developers often have to look back through the commit history to find out why certain changes were made. Commits were often made in some sort of logical order, so you will want to try and preserve that order while rebasing.
also i don't quite understand how merging branches works yet. lets say i have two branches of a simple hello world app, and in one branch it says "hello" and in the other it says "goodbye" how would merge work in that kind of situation?
In a merge, Git will replay changes from the target branch onto your current branch. For a single file, it does this by generating diffs between the two files, and then simply overwriting the lines in your current file if changes were made in the merging file and not in your current file. Let's say you had one branch named "
master". Then you created a new feature branch named "
goodbye" from it. The master branch already contained the line that prints out
"hello", and the feature branch changes that line to print
"goodbye". Then you merge the two branches. Git will make a diff of the file and see that the lines are different, but because the line that prints
"goodbye" was already based on the line that prints
"hello", Git simply overwrites the version that said
"goodbye".
What happens when two lines conflict and one of the two versions was not based on the other version? For instance, another developer changed the version on the master branch to print
"bye bye" before you had the chance to merge your work. The line that you changed to print
"goodbye" was NOT based on the line in the master branch to print
"bye bye". Git will then create what is called a "merge conflict".
At the end of the merge operation, if you have any conflicting changes that Git could not resolve itself, your working tree will be in a "
MERGING" state, and Git will have converted any of your textual* files that contained conflicts to a new "diff" format. Basically, the sections of your file that conflicted will be replaced with BOTH versions of the code, separated by special markers. It's now up to YOU to resolve the merge conflict by choosing which of the two versions of the code you want to keep, and remove the other version and the conflict markers. However, you may also make other changes to the file, in the same way you would normally make changes to it. This happens when you need some code from both versions. Here is an example. This was the original code:
Now, one branch changed the code so the
greet() method would accept a name to greet, and another branch changed the code so the
greet() method would accept a printer to print the greeting to, instead of using standard output. This results in the following merge conflict:
Resolving this conflict is more complex than just picking one of the two versions of the code. We have to apply our
developer's insight™ to make a sensible merge of the code:
* Git can also create diffs for binary files, but you have to install a separate diff provider that can do this for Git.
When you've changed your working tree so that there are no more files with conflict markers, you can add the changes (your conflict resolutions) to the index and then commit the so called "merge commit".
Please note that conflict resolution is one area where your IDE really shines: instead of showing you a file with conflict markers, it will show you a side-by-side diff of the changes in the two different versions with check boxes that will allow you to select which version you want to keep working with. Then, in a third editor it will show you the resulting code, where you can also apply your
developer's insight™ to make further manual edits to the code.
So all of this seems like a lot of pain, right? Why not use
git rebase instead of
git merge every time? Well, you can get merge conflicts when you're performing a rebase too. If the changes that you want to replay on top of the target branch conflict with changes that were made to the target branch in the mean time, you will also have to perform conflict resolution. You have to do this separately for every commit that's involved in the rebase operation, so in the end you're doing just as much work when rebasing as you are when merging. Why pick one over the other?
The difference is in how the commit history looks at the end of the operation. In a merge operation, the commit history looks more messy, with added commits that contain little code and only serve to record a merge between two branches. On the other hand, your history contains all the commits in their original form. When you rebase, the commit history is more linear and there are no extra merge commits. The problem is that rebasing creates new commits (with new commit hashes) for the replayed changes, and throws the old rebased commits away (it doesn't really, they're just not associated with a branch any longer). If a different developer was working on a branch that they based off a commit that you rebased, then they will run into trouble when trying to merge their work into the main branch, because Git thinks that the commits that they were working off of are completely new and not a part of the main branch yet. Your fellow developers will then find out who rebased their commits and then scream at you. This is not figurative or just theoretical. They will literally actually scream at you.
Here's a good way to do things: Before merging your feature/bugfix/hotfix branch to a develop/master/staging/release branch, make sure that your current branch is up to date with the target branch. If your current branch is not shared with other developers (usually in bugfixes, hotfixes and minor features) you can rebase your current branch on the target branch**. This will keep your commit history clean, and there are no other developers to yell at you. Then checkout the target branch and use a
git merge to pull in the feature branch.
When you're working on a shared branch (usually for a major feature), you ALWAYS use
git merge instead of
git rebase. A nifty way to keep the master branch clean is to make an agreement with your fellow developers that all work is merged to a feature-staging branch, and when everybody is done with their part of the major feature and all work has been merged to the feature-staging branch, the feature-staging branch is rebased on the master branch and then merged in. Everybody then throws away their own copies of the feature branch.
** A really good trick to clean up history before merging a bugfix, hotfix or minor feature to master is to use a squash rebase on your branch first. This will squash all your commits into a single commit with a single, clear commit message. That leaves you free to use dumb commit messages while you're working on your bug or feature, and then you can clean it all up before you merge your branch into master. IMPORTANT: When performing a rebase of ANY kind you MUST delete your branch from the origin server after you've merged it to master, because you want to prevent other developers from continuing work on a branch that has been rebased. If you don't do this, screaming will ensue.