Search code examples
gitgit-plumbing

Cannot create commit if the filename argument given to hash-object differs from the original filename


I'm toying with the git plumbing commands to get a better understanding of its inner mechanisms. I'm trying to reproduce a commit without using the git commit command. Let's create a blob:

$ git init
$ echo "I'm an apple" > apple.txt
$ git hash-object -w apple.txt
2d1b0d728be34bfd5e0df0c11b01d61c77ccdc14

Now let's add it to the index but with a different filename:

$ git update-index --add --cacheinfo 100644 2d1b0d728be34bfd5e0df0c11b01d61c77ccdc14 orange.txt

Here's the output of git status:

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   orange.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    orange.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    apple.txt

What is happening here?

apple.txt still exists but is untracked, which seems normal to me as I gave the SHA-1 to the blob object, which only contains the content of the file.

Let's continue and write the tree:

$ git write-tree
abbb70126eb9e77aaa65efbe0af0330bda48adf7
$ git cat-file -p abbb70126eb9e77aaa65efbe0af0330bda48adf7
100644 blob 2d1b0d728be34bfd5e0df0c11b01d61c77ccdc14    orange.txt
$ git cat-file -p 2d1b0d728be34bfd5e0df0c11b01d61c77ccdc14
I'm an apple

Let's finish this operation by creating the commit pointing to this tree:

$ echo "Add fruit" | git commit-tree abbb70126eb9e77aaa65efbe0af0330bda48adf7
57234c35c0d58713d2b4f57b695043e5331afe58
$ git cat-file -p a4386cb82e1f6d1755e47ace1f378df35df31967
tree abbb70126eb9e77aaa65efbe0af0330bda48adf7
author Gregoire Borel <[email protected]> 1527001408 +0200
committer Gregoire Borel <[email protected]> 1527001408 +0200

Add fruit

Now, if I run git status, the output is the same as given above. Why? Also, the commit doesn't seem to have been created:

$ git log
fatal: your current branch 'master' does not have any commits yet

Did I miss something?


Solution

  • You created the commit, but you didn't update any ref to point to it. You're still checked out to an unborn master branch. (Remember by default git log shows the history of what's checked out; and even when you tell it to show "all" it tends toward just what's reachable from refs.)

    You didn't show it above, but when you issued commit-tree it should've printed a hash to standard output (and in my test following your steps, it does). So then referring to that as <commit-hash> you could say

    git merge <commit-hash>
    

    The changing filename is mostly a red herring. Neither the index nor database ever contain an object that knows of apple.txt in this scenario, and so apple.txt is just an untracked file on the work tree as you note.


    Update - as an additional note, you're creating the new commit with no parent which makes sense as there are not yet any commits in your repo (if I'm reading your scenario correctly). In general if adding a new commit to an existing (and checked out) branch, you'd need to give an argument like -p HEAD - and even then the commit-tree command alone won't update the branch.

    If you want to avoid merge (favoring lower-level commands) an alternative would be git update-ref, as in

    git update-ref refs/heads/master 50b7
    

    (This is a bit more dangerous, in that it is more likely to do the wrong thing by assuming you know what you're doing, even if you've made a mistake... But that's the nature of the plumbing commands.)