These notes assume you’re familiar with the basic functions of Git.

The Git repository exists entirely in a single “.git” directory in your project root. Objects in Git are identified by hashes.

  • Blobs – Binary representation of a file.
  • Tree objects – Similar to directories. Points to blobs and other tree objects.
    • Similar to a directory with a list of files (blogs) and other tree objects (sub-directories)
    • The root tree object has the “big picture.” It is basically a snapshot of your repository.
    • If the root directory of your project contains a file (test-file.txt) and a directory (test-dir). Test-dir has a file called test-code.c:
      • Tree 831da3

        • test-dir (Tree 92ads31)
        • test-file.txt (Blob 12asd391)
      • Tree 92ads31
        • test-code.c (Blob 931bac3e)
  • Commit objects – Pointer to a single tree object
    • Commits are basically lightweight snapshots of the code at that point in time. Compression could be used. Also only the changes between revisions are tracked as to keep it compact.
    • Contains (run “git show –format=raw”)
      • Hash of the root tree object at the time of commit
      • Hash of parent commits (history of commits)
      • Author’s name/email
      • Committer’s name/email
      • Commit message
    • In the above example, the commit object has a hash of FF450. You typically only need the first 4 letters of of the hash to find the object.
  • Tag objects – Points to a single commit object, contains metadata.
  • References – Maps to a hash so you don’t have to memorize hashes. Points to a single object. Typically a Commit or Tag object.
    • .git/refs/heads
      • The “master” branch is a ref. The file contains the hash.
      • git show –oneline master
      • git rev-parse master
    • The reference HEAD points to the end of the current branch rather than the commit.
      • cat .git/HEAD will point to the refs/heads/master file.
      • When pointing to a commit object, you will be in a “detached HEAD state” which means you’re not on a branch.
  • Branches
    • They are basically pointers to a specific commit. That’s it.
    • Branches are just references. You could find them under .git/refs/heads/[branch name]
    • Initially this will point the current commit object.
    • When you make a commit on a branch, it simply just changes the current branch (.git/HEAD) to point to the newly created commit object.
    • Local Branches
    • Remote-tracking Branches
  • Tags
    • Tags are primarily use to label revisions at specific commit points. Unlike branches, they are immutable references.
    • You shouldn’t change or delete a tag once release publicly.
    • .get/refs/tags
    • By default tags created with ‘git tag [name of tag]’ is just a lightweight tag. It is only a reference to the commit object. Run ‘git cat-file -p [name of tag]’ to see that it’s not a tag object.
    • Annotated tags on the other hand could be created with ‘git tag -a -m “[message]” [name of tag]
      • Have their own author information. Who created the tag?
      • This is a tag object
      • Contains pointer to commit
      • Tag message
      • Timestamped to help keep track of release dates
      • Information about the tagger
      • Can be signed with a GPG key to prevent commits or email spoofing
    • Use git show to see what’s contained within a tag.
  • Merging
    • We create branches when we need to add a new feature or fix a bug.
    • The way Git handles merges behind the scenes isn’t intuitive.
      git init
      echo "index file" > index.html
      git add index.html
      git commit -am "first commit"
      git checkout -b new-feature
      echo "new feature file" > new-feature.html
      git add new-feature.html
      git commit -am "add new feature"
      git checkout master
      git checkout -b bug-fix
      vim index.html (modify the file)
      git commit -am "fix bug"

      Running the above you’ll have three branches: master, new-feature, and  bug-fix.

      git checkout master
      git log --oneline
      d0da44b first commit
      git checkout new-feature
      git log --oneline
      33d9fc5 add new feature
      d0da44b first commit
      git checkout bug-fix
      git log --oneline
      b8bd4cc fix the bug
      d0da44b first commit
    • Now let’s try to merge the bug-fix branch into the master.
    • Fast-forward means that the commits in bug-fix were upstream from master. Git will simply move the pointer up the tree to bug-fix.
    • Now let’s try to merge the new-feature branch into the master.
    • We’ll notice here, Git didn’t perform a fast-forward. The reason being, the new-feature branch isn’t upstream from master (remember master was pointing to the same level of the tree as new-feature).
    • The merge creates a new commit object. This commit object has two parents. This is called a merge commit. This is particularly useful in code review. When a developer pushes a feature branch out for review, the reviewer could create the merge commit which contains metadata on the reviewers and useful comments.
    • If you want only fast-forward merges, you could rebase the branch before the merge.
  • Remotes
    • Recall that Git stores the entire repository under the .git directory. The entire history could be traversed and the a snapshot of the project could be built.
    • A Git remote is just another Git repository.
    • Git only needs a working tree to find out which changes have been made since the last commit.
    • bare repository only contains the project’s history without requiring a working tree. This is where collaborators could push and pull from. git-bare-repo
      • The master branch doesn’t exist yet.
      • bare is set to “true”
      • You cannot add files to a bare repository. It is meant to be cloned, pulled, and pushed to/from.
  • Clone
    • git-clone
    • .git/config has a few extra lines.
      • remote “origin”
        • “origin” is the default name given a repository’s main remote.
        • fetch = which references should be retrieved when performing a “git fetch”
        • url = the URL of the repository
      • branch “master”
        • Configuration for remote-tracking branch
  • Push

    • The master branch now exists.
    • Specify the location of the repository you want to push to (origin) and the branch (master).
    • Generally you don’t want to run a naked “git push” because you may push all remote-tracking branches. There usually isn’t a problem with this but you run into the chance of pushing changes you don’t want other collaborators to pull.
    • Push updated the remote’s master to point to afd2 and the afd2 commit object as well as any tree or blob objects related to that change.
  • Remote-Tracking Branches
    • From .git/config, a remote-tracking branch is the line following [branch “master”]
    • This means the configuration is for the current local master branch.
    • The “remote” and “merge” configurations specifies when this branch is fetched, it should fetch the master branch from the origin remote. A local copy of the remote branch is stored.
  • Fetch
    • git fetch – updates your remote-tracking branches under /refs/remote/origin, it doesn’t change your local branches under /refs/head
    • [remote “origin”]
      • This is a mapping of remote references to local references. All references at the remote origin is placed locally in refs/remotes/origin/*
    • fetch does not create a local branch because you may not want it in your local repository.
    • If you want to create a local branch, run “git checkout new-feature”

      • Git will automatically create the new-feature reference (which points to the  same commit as the remote new-feature branch).
      • Git will also create a remote-tracking branch entry under .git/config
  • Pull
    • Very similar to git clone. git pull <remote> <branch>
      • git fetch <remote>
      • Uses .git/FETCH_HEAD to figure out if <branch> has a remote-tracking branch that should be merged.
      • git merge if required
    • Git overrides the contents of FETCH_HEAD every time you run “git fetch”
    • To demonstrate this, clone the repository and modify the README file on the new-feature branch. After you’ve finished, go back to the original clone.

      • Here we perform a manual “git pull.” By looking at .get/refs/heads/new-feature and FETCH_HEAD, we know there were changes in the new-feature branch. We simply perform a merge on FETCH_HEAD to apply the changes.