13

I am reading about git objects: blob, tree, commit, tag. In order to have a better understanding of how git works, I tried some low level command like write-tree and commit-tree.

  1. mkdir test; cd test --> git init
  2. I create a file and git add file. I can see a blob and tree object are generated in .git/objects
  3. git write-tree to print the current treeID
  4. git commit-tree treeID -m "commit a tree" to commit this tree. After this operation, a commit object is generated and I can see it does contain author, date, etc. However, I can't check my commits using git log, the error is : fatal: bad default revision 'HEAD'.

After above operations, when I run git status, I see the file is still in the index waiting for commit. What is the use of commit-tree and what's the difference between commit-tree and `commit'?

hakunami
  • 2,351
  • 4
  • 31
  • 50
  • 2
    Have you read [this section](http://git-scm.com/book/en/v1/Git-Internals-Git-Objects) of the Pro Git book? It should be of interest to you, because it details how to create commits using "Plumbing" (i.e. low-level Git) commands. – jub0bs Apr 14 '15 at 09:37
  • 1
    You should probably edit your question and add the exact sequence of commands you used; that would make it easier for people here to pinpoint what's wrong in it. – jub0bs Apr 14 '15 at 10:24

2 Answers2

7

git-commit - Record changes to the repository

Stores the current contents of the index in a new commit along with a log message from the user describing the changes.

git commit "records changes to the repository"

Diagrammatic representation of git-commit is shown here at SO

git-commit-tree - Create a new commit object

Creates a new commit object based on the provided tree object and emits the new commit object id on stdout.

This is usually not what an end user wants to run directly. Creates a new commit object based on the provided tree object and emits the new commit object id on stdout. The log message is read from the standard input, unless -m or -F options are given.

MartyIX
  • 27,828
  • 29
  • 136
  • 207
Tharif
  • 13,794
  • 9
  • 55
  • 77
  • 1
    both `git commit` and `git commit-tree` create commit object and can accept user message, however, `git commit` with snapshots the index HEAD, but `git commit-tree` will not guarantee the commit object is from HEAD index, it could be from any (sub)tree of the whole tree structure, am I right? so after `git commit-tree`, if I have a api to put certain commit on the HEAD, then the result will be like `git commit` ? – hakunami Apr 15 '15 at 00:57
  • 3
    This answer is technically accurate but I think it doesn't stress an incredibly important point. `git-commit-tree` is a low level command which commits a single tree object but does not perform any of the follow-up reference and Head work that `git-commit` does. If you are manually performing `git commit-tree` commands without the follow-up reference work, you could easily create an unreachable tree which would be removed by the Git garbage cleaner. See [Git from the bottom up](https://jwiegley.github.io/git-from-the-bottom-up/1-Repository/4-how-trees-are-made.html) pages 8-10 for more info. –  Mar 21 '17 at 13:31
0

git-commit(1) is the high-level command that you want to use almost all of the time. git-commit-tree(1) is a lower-level command which is not a daily-use command.

git-commit(1) is used when you commit new changes interactively. But I guess I don’t have to elaborate on that.

git-commit-tree(1) is useful when you:

  1. Want to explicitly pick the parents of a commit
  2. You already have a tree for the commit

Say that I want to effectively “squash” all the commits in the repo down to a single commit. But “squash” is a rebase operation which is excessive since it combines changes, and I don’t need to combine any changes since I already know what snapshot I want—the current one. So:[1] [2]

$ git commit-tree -m Init HEAD^{tree}
1a875b50b508c70e44a4e6300fd80f580aed3d99

(That becomes a parentless commit since I didn’t specify any parents with -p.)

That’s the SHA1 of the commit. I better put it on a branch so that the garbage collector won’t collect it a few months from now.

$ git branch new-start 1a875b50b508c70e44a4e6300fd80f580aed3d99

Another thing you can do is create whatever history you want using existing commits and their trees. Say for example that I want to make a history of the Git repository which only consists of versions v1.0.0, v2.0.0, and v2.40.0 and call the branch small-git:

git clone https://github.com/git/git/ git-repo
cd git-repo
first=$(git commit-tree -m v1 v1.0.0^{tree})
second=$(git commit-tree -p $first -m v2 v2.0.0^{tree})
third=$(git commit-tree -p $second -m v2.40.0 v2.40.0^{tree})
git branch small-git $third

You can verify with this (all diffs should be empty):

$ # While on `small-git`
$ git diff v2.40.0
$ git diff @^ v2.0.0
$ git diff @^^ v1.0.0

Notes

  1. See man gitrevisions for the ^{tree} syntax
  2. This can be accomplished by way of other means, like e.g. git checkout --orphan new-start && git commit -m Init. So it’s not like you need this command to do it.
Guildenstern
  • 2,179
  • 1
  • 17
  • 39