In previous blog post, we learned how to create a git repository and commit first using Visual Studio and then achieve the equivalent of same using the native git commands. In this blog post, we’ll learn how to see the specifics of commit or how git stores this information.
Let’s analyze commit made by using Visual Studio, using the project ConsoleApp02. If we navigate to the project directory and do a git log, we’ll see below output:
If we do see the contents of .gitignore, it will be something like below and more:
The purpose of the .gitignore file is to allow you to ignore files, such as editor backup files, build products or local configuration overrides that you never want to commit into a repository. Without matching .gitignore rules, these files will appear in the untracked files section of git status output. Visual Studio has some built-in information about the project you created, and so it will auto populate the .gitignore file to avoid objects/files which should not be part of source code repository.
You will also notice that the commit is followed by SHA-1 hash. Git creates a unique SHA-1 hash for each object such as file, commit, tree etc and use it for maintaining integrity.
If we want to see the contents of the commit made, we can get is using git cat-file command:
So what is the tree object? The tree object works like a dictionary. It maps names to SHA-1 hashes. Behind those hashes there may be simple files (represented as blobs) or other tree objects. To see what is contained in the tree object, we can run git ls-tree command:
Here we can notice that the git generates the SHA-1 of the contents of the file .gitignore as 3c4efe206bd0e7230ad0ae8396a3c883c8207906 and stores it. Same goes for .gitattributes file. File contents are themselves stored as a blob object. Notice that filename .gitattributes or .gitignore is itself not associated with hash created.
Since ConsoleApp02 is a directory, its contents are hashed and it is created as 37fd43a0668f401fe9a4aaa9fdda34b1e6e66ecc. If we do a git ls-tree again on this, we will see below output:
So in a nutshell, our commit object contains below information:
commit message => “Add project files.”
commiter => Mohit Goyal followed by email id
commit date => Wed Nov 15 14:12:50 2017 +0530
author => Mohit Goyal followed by email id
authoring date => Wed Nov 15 14:12:50 2017 +0530
hash of entire working directory => 0fd4a18d5536e5037c12a8dc4b3ff4f63398e188
) => a7d6ca3168f8b67f00118a6afa1fff0c4c9b8a91
It’s all the meta data plus the hash of the root tree object. And of course Git creates a SHA-1 hash from those contents. The commit hash.
You can not change a single thing about this commit without getting a different SHA-1 commit hash. Want to change the commit message? The commit message is part of the content that is used to produce the SHA-1 hash, changing it will change the commit hash. What if we just add a whitespace somewhere in .gitignore file? Its new SHA-1 hash would not be the same anymore. Not only that, if you look closely to the commit file contents, it itself contains the parent commit hash.
If we hover over the commit inside Visual Studio, we can get same details:
In this blog post, how git stores commit details and maintains integrity behind the scenes. We also learn how to view same information inside Visual Studio.