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?
-
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 Apr 17 '13 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? – user2291590 Apr 17 '13 at 17:42
-
You just pipe a valid tree to it, just as you would any other object, such as blob. – CB Bailey Apr 17 '13 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 Oct 17 '13 at 13:11
-
1In particular, `git rev-parse master` and `git cat-file commit master | git hash-object -t commit --stdin` will give the same result. – jthill Oct 17 '13 at 13:13
2 Answers
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\nyour@email.com\n2013 12:20:15 +0200\ncommittername\ncommitter@email.com\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 your@email.com 2013 12:20:15 +0200 committername committer@email.com
Edit: fixed and improved

- 4,427
- 3
- 31
- 42
-
-
@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 Aug 16 '17 at 12:29
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 newgit
worktree and initiailizes it. It is named after the first argument (herenewgitdir
), 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 createdgit
-workdir. Note that this argument must not start with a/
, else the command fails. Heredir=path/to
andname=file
. If left away, the new file is called "dummyfile" in the top of thegit
-workdir.obid="$(git hash-object -w --stdin)"
then stores the object with the information read fromstdin
into thegit
repo and stores the SHA (object id) of the new object in the variableobid
. In the example, the contents ishellOw W0rld
followed by anNL
.git mktree < <(printf '100644 blob %q\t%q\n' "$obid" "$name")
is nearly the same asprintf '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 need100755
here.treeid="$(
...)"
then assigns this to the given variabletreeid
if [ . = "$dir" ]; then git read-tree -i "$treeid";
this reads this newly created tree into thegit
staging area for later commit. However this is for the case, that the file shall be directly in thegit
-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 thegit
-workdir. The nice thing is, thatgit
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 commitgit reset --hard
then syncs thegit
-worktree with the latest commit.
Changes for bare
variant:
Everything works similar, except
git commit
(this needs a workdir) andgit 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:
See also https://stackoverflow.com/a/25556917
This here needs
bash
, which means, it works for Windows 10, too

- 9,583
- 5
- 55
- 60
-
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 Jul 10 '20 at 17:21