Git is not Subversion • The two are very different • Not just in operation, but in the way you must think • Subversion commit: A snapshot in time • Git commit: A set of changes; a diff
Git is not Subversion • The two are very different • Not just in operation, but in the way you must think • Subversion commit: A snapshot in time • Git commit: A set of changes; a diff • Subversion branch: A cheap copy of the tree • Git branch: A convenient, changable identifier for a commit and its ancestors
Basic commands • Basic git commands are quite similar to subversion • Both use subcommands of a master program, e.g. git status and svn status • Git has man pages for each command: git command --help • Most commands are (more or less) identical • Be warned: there are some differences
Setting user info • Git needs to know who you are! • The very first git commands you should run: • git config --global user.name ' your name ' • git config --global user.email email • This information will identify your commits
Setting up your working copy Start off by getting a copy of the repository: $ git clone url dir Initialized empty Git repository in dir /.git/ remote: Counting objects: 77, done. remote: Compressing objects: 100% (76/76), done. remote: Total 77 (delta 49), reused 0 (delta 0) Receiving objects: 100% (77/77), 1.97 MiB | 1.26 MiB/s, done. Resolving deltas: 100% (49/49), done. $ cd dir clone branch edit status diff stage check commit merge/push
Starting work on a feature A newly-cloned repository will start with the master branch checked out. It’s a good idea to put every feature in its own branch, unless it’s a trivial bugfix: $ git co -b branch This will create the branch branch, and switch to it. Now you can actually start to edit files. clone branch edit status diff stage check commit merge/push
Checking status $ git status # On branch branch # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: app/models/Comment.php # modified: app/views/CommentView.php # # Untracked files: # (use "git add ..." to include in what will be committed) # # app/models/CommentThread.php # www/comments/threads/ no changes added to commit (use "git add" and/or "git commit -a") clone branch edit status diff stage check commit merge/push
Seeing what has changed To see details of what has changed, use: $ git diff diff --git a/app/models/Comment.php b/app/models/Comment.php index 07ac8b3..627b51b 100644 --- a/app/models/Comment.php +++ b/app/models/Comment.php @@ -1,4 +1,5 @@ require once('app/models/Article.php'); +require once('app/models/CommentThread.php'); class Comment extends Model { @@ -16,5 +17,75 @@ clone branch edit status diff stage check commit merge/push
Preparing a commit With git, you must “stage” changes to be committed. This gives you full control and helps ensure that you don’t commit anything you don’t intend. $ git add app/models/Comment*.php $ git add app/views/CommentView.php $ git add www/comments/threads If you want to stage everything, you could also do: $ git add . clone branch edit status diff stage check commit merge/push
Seeing what will be committed To see details of what will be committed, like git diff, use: $ git status -v This will show you every change waiting to be committed, in the same style as git diff. clone branch edit status diff stage check commit merge/push
Making a commit Actually making a commit is very simple: $ git commit -m message If you want to provide a longer commit message, you can use your editor by running: $ git commit Note that the first line of a commit message should be a short summary, and the second line must be blank. Any further lines can contain more details about the commit. clone branch edit status diff stage check commit merge/push
Merging your branch back After the feature is finished, the branch needs to be merged back. First, change to master: $ git checkout master Next, pull any recent changes from the central repository: $ git pull Then merge in the branch and push it back to the main repository: $ git merge branch $ git push clone branch edit status diff stage check commit merge/push
Remember! • Git is not Subversion • All commits are local until you push them! • Make sure you push branches regularly (e.g. daily) • If your machine dies, unpushed commits will be lost • Git commands work on the whole repository, not just the current directory and those below • Git works best when you frequently make many small commits
Other useful commands There are two other commands that are useful to know, although you may not use them every day: • git stash — “stash” and restore your changes to the working copy, if you need it to be clean • git bisect — quickly narrow down the commit which introduced a bug (to be covered later)
git stash • You may find you need to interrupt what you’re doing in order to fix a bug, without wanting to commit • Although it’s possible to go back and edit commits that haven’t been pushed, it can be troublesome • git stash will save the current state of your working copy to a stack, then reset it to being clean • You can switch branches or do whatever is necessary to fix the immediate issue • When you’re done, switch back to the branch you were on and run git stash pop to return to where you last were
git stash ...work work work need to fix an urgent bug! $ git stash your working copy is now clean, with no uncommitted changes $ git checkout master $ git pull do emergency fix $ git add . $ git commit -m 'Emergency fix for bug' $ git push $ git stash list stash@{0}: On mybranch: Added threading stash@{1}: On other-branch: Fixed widget $ git checkout mybranch $ git stash pop work work work ...
History Styles • History in Subversion is mostly linear: • Monotonically increasing global revision IDs • Branching can be expensive, slow, and inconvenient • Branches are often only made for large features
History Styles • History in Subversion is mostly linear: • Monotonically increasing global revision IDs • Branching can be expensive, slow, and inconvenient • Branches are often only made for large features • In git, history tends to be very branchy: • No sequential revision IDs • Branching is easy, fast, and convenient • Tend to create tiny branches for each individual feature
Subversion branching • In Subversion, a branch is a cheap copy of trunk (or another branch) • Once you commit to a branch, that commit always belongs to that branch
Subversion branching • In Subversion, a branch is a cheap copy of trunk (or another branch) • Once you commit to a branch, that commit always belongs to that branch • Branches can be deleted without being merged, but are still recoverable by traversing history
Subversion branching • In Subversion, a branch is a cheap copy of trunk (or another branch) • Once you commit to a branch, that commit always belongs to that branch • Branches can be deleted without being merged, but are still recoverable by traversing history • Merging or cherry-picking loses information about where the changes came from (unless mentioned in the commit message)
Subversion branching • In Subversion, a branch is a cheap copy of trunk (or another branch) • Once you commit to a branch, that commit always belongs to that branch • Branches can be deleted without being merged, but are still recoverable by traversing history • Merging or cherry-picking loses information about where the changes came from (unless mentioned in the commit message) • Merging also squashes an entire branch worth of changes into a single commit
Subversion branching • In Subversion, a branch is a cheap copy of trunk (or another branch) • Once you commit to a branch, that commit always belongs to that branch • Branches can be deleted without being merged, but are still recoverable by traversing history • Merging or cherry-picking loses information about where the changes came from (unless mentioned in the commit message) • Merging also squashes an entire branch worth of changes into a single commit • A bit like copying an entire directory with cp -r
Subversion branching You change to trunk and run svn merge .../mybranch with the right revision range to import the changes, and commit trunk mybranch yourbranch
Subversion branching Notice that this commit is on trunk and embodies all the changes from mybranch, but Subversion does not remember where it came from – it looks like a normal commit
Git branching • In Git, a branch is a convenient non-permanent name for a commit • The branch a commit belongs to is any branch it is reachable from • This means that if you trace a path back from a branch to the root, all of the commits you pass through are reachable from that branch
Git branching • In Git, a branch is a convenient non-permanent name for a commit • The branch a commit belongs to is any branch it is reachable from • This means that if you trace a path back from a branch to the root, all of the commits you pass through are reachable from that branch • If an unmerged, unpushed branch is deleted, it is gone*
Git branching • In Git, a branch is a convenient non-permanent name for a commit • The branch a commit belongs to is any branch it is reachable from • This means that if you trace a path back from a branch to the root, all of the commits you pass through are reachable from that branch • If an unmerged, unpushed branch is deleted, it is gone* • Cherry-picking loses ancestry by default, but can be easily changed
Git branching • In Git, a branch is a convenient non-permanent name for a commit • The branch a commit belongs to is any branch it is reachable from • This means that if you trace a path back from a branch to the root, all of the commits you pass through are reachable from that branch • If an unmerged, unpushed branch is deleted, it is gone* • Cherry-picking loses ancestry by default, but can be easily changed • Merging keeps the branch, but loses the name attached to it
Git branching • In Git, a branch is a convenient non-permanent name for a commit • The branch a commit belongs to is any branch it is reachable from • This means that if you trace a path back from a branch to the root, all of the commits you pass through are reachable from that branch • If an unmerged, unpushed branch is deleted, it is gone* • Cherry-picking loses ancestry by default, but can be easily changed • Merging keeps the branch, but loses the name attached to it • A bit like creating a symlink with ln -s
Git branching Notice that this commit is on trunk, and all of mybranch is reachable from it. If you delete mybranch, then its history remains. trunk mybranch yourbranch
That’s great and all, but. . . • . . . what are the advantages? • Git’s branch tracking lets you do complicated merges really easily • Long-lived feature branches are trivial • Branching and merging is no longer something to fear! • Let’s see an example
Things to remember • Git has very powerful merge tracking • Because it tracks content, it can even handle changes to a file that has been renamed in another branch
Things to remember • Git has very powerful merge tracking • Because it tracks content, it can even handle changes to a file that has been renamed in another branch • Problems are only likely to come up if: • the same part of a file is edited in both branches and git can’t work out a resolution
Things to remember • Git has very powerful merge tracking • Because it tracks content, it can even handle changes to a file that has been renamed in another branch • Problems are only likely to come up if: • the same part of a file is edited in both branches and git can’t work out a resolution • a file is deleted (not moved) in one branch and edited in another
Things to remember • Git has very powerful merge tracking • Because it tracks content, it can even handle changes to a file that has been renamed in another branch • Problems are only likely to come up if: • the same part of a file is edited in both branches and git can’t work out a resolution • a file is deleted (not moved) in one branch and edited in another • two files are created with the same path in each branch
Final bit of git magic (for now) • Sometimes a bug is found that has probably existed for a long time, as an unexpected side-effect of an earlier change • If you know that it used to work, then tracking down the commit which actually caused the bug can be a big task • This is where git bisect comes in useful
git bisect • git bisect allows you to do a binary search on commits to find the one that caused the bug • You could manually jump between revisions, but that’s very time-consuming • With git bisect you must still find a good commit, but you can just keep jumping back in history to find it • Just tell git whether a commit is good or bad, and it does the rest • Note: needs a clean working copy when you begin
git bisect We know that the current commit is bad and contains a bug that wasn’t there before, but we don’t know when it was introduced. We start git bisect, (using git stash to save changes from our working copy if necessary): $ git bisect start
git bisect We know that the current commit is bad and contains a bug that wasn’t there before, but we don’t know when it was introduced. We start git bisect, (using git stash to save changes from our working copy if necessary): $ git bisect start We now mark the current commit as being “bad” (i.e. it has the bug) $ git bisect bad
git bisect We then jump back in history (say fifteen commits at a time) until we find a commit without the bug. $ git checkout HEAD~15 Note: To better illustrate what git checkout is doing, the animation goes back 5 commits at a time.
git bisect We then jump back in history (say fifteen commits at a time) until we find a commit without the bug. $ git checkout HEAD~15 Note: To better illustrate what git checkout is doing, the animation goes back 5 commits at a time.
git bisect We then jump back in history (say fifteen commits at a time) until we find a commit without the bug. $ git checkout HEAD~15 Note: To better illustrate what git checkout is doing, the animation goes back 5 commits at a time.
git bisect We then jump back in history (say fifteen commits at a time) until we find a commit without the bug. $ git checkout HEAD~15 Note: To better illustrate what git checkout is doing, the animation goes back 5 commits at a time.
git bisect This commit doesn’t have the bug, so we mark it as “good” (i.e. the bug isn’t there). $ git bisect good As soon as we do this, git automatically checks out the next com- mit we should test.
git bisect After noting down the commit ID for later examination, you need to go back to where you started to carry on working. All you need to do is finish git bisect and perhaps run git stash pop if we had any stashed changes from before. $ git bisect reset
git bisect After noting down the commit ID for later examination, you need to go back to where you started to carry on working. All you need to do is finish git bisect and perhaps run git stash pop if we had any stashed changes from before. $ git bisect reset Done!
git bisect summary $ git bisect start The current commit has the bug $ git bisect bad Jump back 10 commits (repeat until good commit found) $ git checkout HEAD~10 When this commit does not have the bug. .. $ git bisect good Git automatically checks out commits to test – decide if pass/fail $ git bisect good|bad Eventually git tells you which commit is bad Investigate... $ git bisect reset Back where you started work work work ...