Let's now see what that looks like from a more technical standpoint, keeping an eye on the evolution of files inside our project directory and local repo.

  1. We start by initializing an empty repo. Notice the .git directory that showed up. Let's look inside, focusing on stuff related to what we covered already. 
    1. First, there's a HEAD file: looking inside it you see just one line stating that HEAD is currently tracking the tip of the master branch: "refs: refs/heads/master"
    2. And indeed the subdirectory refs/heads exists, but there's no master file in there yet. You also see an empty refs/tags directory. 
    3. objects/ will be our sort of database, with all objects stored in it, including blobs, trees and commits. 
  2. So let's create a file. Checking: nothing changed in the local repo.
  3. Let's now stage it and look inside objects/ 
    1. Its contents is sliced in subdirectories based on the first byte of the object’s identities, hence the first 2 hexadecimal digits. This spreads files out in order to work around a limitation of the NTFS file system regarding the maximum number of files in a single directory. 
    2. So the identity of a file is that first byte encoded in the subdirectory's name, followed by the remaining 19 bytes, hence 38 hexadecimal digits, that are encoded in the file’s name. 
    3. Now let's look into that file. If we try and open it in an editor or dump it in our command line, it's garbled because it's been compressed using zlib
    4. But we can use a plumbing command to pry into it: git cat-file. Its -t option gives us the object's type, and its -p option (P for Pretty) shows its content in a readable form. 
    5. We can see this is indeed a blob file with the content from the file we created earlier. 
    6. Also notice the new index file at the root of our Git repo. Same thing here: it's not readable as-is. The plumbing command git ls-files will show us what's inside. 
  4. Now let's commit our file 
    1. You can see a master ref appeared in refs/heads. Looking inside it, we can confirm there's just one line in there, which refers to the underlying commit. 
    2. And indeed that commit object exists in our objects/ database. 
    3. Now if we look inside this file with git cat-file, we see it's indeed a commit object, that contains our metadata and a reference to a tree that was just created.  Looking at this tree, we find a reference to the blob for the file we had created earlier, with its path name. 
  5. Now let's create a new dev branch, and make HEAD point on it so it becomes the active branch. We can do both in one go with the git checkout -b dev command. Looking inside .git/refs/heads, we now find a dev file pointing to the same commit as our master branch (which makes sens, as they both label the same history point so far). We can also confirm that the HEAD file now references the dev branch: "ref: refs/heads/dev".
  6. Now if we modify our file and commit it again, we can see the dev branch moved ahead to a new reference, as confirmed by looking inside the .git/refs/heads/dev file. This references a new commit, which itself references a new tree, which in turn references a new blob containing the updated snapshot of our file.

In summary, creating a commit that changed our file yielded a new blob, a new tree referencing the blob, and a new commit referencing the tree.