5

How can i manually build git commit objects using git hash-object? I now it works with blobs, and its documentation says it can build different objects by using -t but how do you build a commit with that?

5
  • You'll need to make some tree objects to make a commit object. You can use an index file and write-tree or just mktree. You can use hash-object if you really want but it seems like a lot of hassle to me.
    – CB Bailey
    Commented Apr 17, 2013 at 16:59
  • Do you know how to use hash-object to do that? I am aware of the existence of git commit-tree but can you do that with git hash-object? Commented Apr 17, 2013 at 17:42
  • You just pipe a valid tree to it, just as you would any other object, such as blob.
    – CB Bailey
    Commented Apr 17, 2013 at 18:38
  • You can see raw contents with git cat-file, e.g. git cat-file commit master, git cat-file tree master^{tree}.
    – jthill
    Commented Oct 17, 2013 at 13:11
  • 1
    In particular, git rev-parse master and git cat-file commit master | git hash-object -t commit --stdin will give the same result.
    – jthill
    Commented Oct 17, 2013 at 13:13

2 Answers 2

4

Here's a complete and working example of a script that creates a commit object without ever running git commit:

mkdir project
cd project
git init

hash=`echo -n "" | git hash-object -w --stdin`

tree=`echo -e "100644 blob ${hash}\temptyfile" | git mktree`

commit=`echo -e "yourname\[email protected]\n2013 12:20:15 +0200\ncommittername\[email protected]\n2013 10:13:15 +0200" | git commit-tree ${tree}`

git update-ref refs/heads/master ${commit}

To verify that the script created a commit containing an empty file, run:

git checkout --
git log --oneline
#2abbdc2 yourname [email protected] 2013 12:20:15 +0200 committername [email protected]

Edit: fixed and improved

2
  • is it "\tempfile" or "\temtyfile" ?
    – qbolec
    Commented Jul 19, 2016 at 9:38
  • @qbolec \t stands for TAB character, so yes, it's \temptyfile. However giving it wrong as \tempfile this only means, it shows up as empfile in the git-worktree. So the filename used for git hash-object does not neccessarily need to be the same for git mktree.
    – Tino
    Commented Aug 16, 2017 at 12:29
2

For the benefit of the reader:

The accepted answer from Arialdo Martini is fully correct and explains how to create an empty git repo with proper plumbing commands. Please note that his variant works for bare repositories, too (you can create emptyfile in the git-dir with no bad sideeffects).

This answer here sums it up into a script with a minor tweak: The contents of the file is taken from "stdin" and the file can be placed into a sub-directory of the worktree.

Script git-init-with-file-from-stdin.sh new-git-workdir filename 'commit message':

#!/bin/bash

mkdir "$1" &&
cd "$1" &&
git init &&

dir="$(dirname "$2")" &&
name="$(basename "${2:-dummyfile}")" &&

obid="$(git hash-object -w --stdin)" &&
treeid="$(git mktree < <(printf '100644 blob %q\t%q\n' "$obid" "$name"))" &&

if [ . = "$dir" ]; then git read-tree -i "$treeid";
else git read-tree --prefix="$dir/" -i "$treeid"; fi &&

git commit -m "${3:-automatic commit}" &&
git reset --hard

Explained for the call

    ./git-init-with-file-from-stdin.sh newgitdir path/to/file <<< "hellOw W0rld"
  • mkdir "$1", cd "$1", git init creates the new git worktree and initiailizes it. It is named after the first argument (here newgitdir), which is assumed to name a nonexisting directory in an existing path.

  • dir="$(dirname "$2")", name="$(basename "${2:-dummyfile}")" extracts the path and the name part of the second argument, which defines the wanted filename. The path is relative to the created git-workdir. Note that this argument must not start with a /, else the command fails. Here dir=path/to and name=file. If left away, the new file is called "dummyfile" in the top of the git-workdir.

  • obid="$(git hash-object -w --stdin)" then stores the object with the information read from stdin into the git repo and stores the SHA (object id) of the new object in the variable obid. In the example, the contents is hellOw W0rld followed by an NL.

  • git mktree < <(printf '100644 blob %q\t%q\n' "$obid" "$name") is nearly the same as printf '100644 blob %q\t%q\n' "$obid" "$name" | git mktree and creates a proper git-tree. 100644 is the usual file mode. If you want to create executables, you need 100755 here.

  • treeid="$(...)" then assigns this to the given variable treeid

  • if [ . = "$dir" ]; then git read-tree -i "$treeid"; this reads this newly created tree into the git staging area for later commit. However this is for the case, that the file shall be directly in the git-workdir.

  • else git read-tree --prefix="$dir/" -i "$treeid"; fi is the same for the case, when you want to put it into a subdirectory of the git-workdir. The nice thing is, that git creates all intermediate directory nodes for you, automatically. (Sadly --prefix="./" produces an error.)

  • git commit -m "${3:-automatic commit}" then uses the well-known commit to create the commit

  • git reset --hard then syncs the git-worktree with the latest commit.

Changes for bare variant:

  • Everything works similar, except git commit (this needs a workdir) and git reset (you do not need it).

  • replace

    git init &&
    

    with

    git init --bare &&
    

    also replace

    git commit -m "${3:-automatic commit}" &&
    git reset --hard
    

    with a similar sequence as seen in the accepted answer:

    commitid="$(git commit-tree "$(git write-tree)" <<< "${3:-automatic commit}")" &&
    git update-ref "refs/heads/master" "$commitid"
    

Remarks:

1
  • Do you know, If I already has file in index and replacing it using git update index command do i need to remove old object manually?
    – zdm
    Commented Jul 10, 2020 at 17:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.