Here we will take a look at how to manipulate git at a more raw level, in case you would like to write a tool that generates new blobs, trees or commits in a more artificial way. If you want to write a script that uses more low-level git plumbing to do something new, here are some of the tools you'll need.
Creating a blob in your Git repository and getting a SHA back is pretty easy. The git hash-object command is all you'll need. To create a blob object from an existing file, just run it with the '-w' option (which tells it to write the blob, not just compute the SHA).
$ git hash-object -w myfile.txt 6ff87c4664981e4397625791c8ea3bbb5f2279a3 $ git hash-object -w myfile2.txt 3bb0e8592a41ae3185ee32266c860714980dbed7
The STDOUT output of the command will the the SHA of the blob that was created.
Now lets say you want to create a tree from your new objects. The git mktree command makes it pretty simple to generate new tree objects from git ls-tree formatted output. For example, if you write the following to a file named '/tmp/tree.txt' :
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 file1 100644 blob 3bb0e8592a41ae3185ee32266c860714980dbed7 file2
and then piped that through the git mktree command, Git will write a new tree to the object database and give you back the new sha of that tree.
$ cat /tmp/tree.txt | git mk-tree f66a66ab6a7bfe86d52a66516ace212efa00fe1f
Then, we can take that and make it a subdirectory of yet another tree, and so on. If we wanted to create a new tree with that one as a subtree, we just create a new file (/tmp/newtree.txt) with our new SHA as a tree in it:
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 file1-copy 040000 tree f66a66ab6a7bfe86d52a66516ace212efa00fe1f our_files
and then use git mk-tree again:
$ cat /tmp/newtree.txt | git mk-tree 5bac6559179bd543a024d6d187692343e2d8ae83
And we now have an artificial directory structure in Git that looks like this:
. |-- file1-copy `-- our_files |-- file1 `-- file2 1 directory, 3 files
without that structure ever having actually existed on disk. Plus, we have
a SHA (
5bac6559) that points to it.
We can also do tree manipulation by combining trees into new structures using
the index file. As a simple example, let's take the tree we just created and
make a new tree that has two copies of our
5bac6559 tree in it
using a temporary index file. (You can do this by resetting the GIT_INDEX_FILE
environment variable or on the command line)
First, we read the tree into our index file under a new prefix using the git read-tree command, and then write the index contents as a tree using the git write-tree command:
$ export GIT_INDEX_FILE=/tmp/index $ git read-tree --prefix=copy1/ 5bac6559 $ git read-tree --prefix=copy2/ 5bac6559 $ git write-tree bb2fa6de7625322322382215d9ea78cfe76508c1 $>git ls-tree bb2fa 040000 tree 5bac6559179bd543a024d6d187692343e2d8ae83 copy1 040000 tree 5bac6559179bd543a024d6d187692343e2d8ae83 copy2
So now we can see that we've created a new tree just from index manipulation. You can also do interesting merge operations and such in a temporary index this way - see the git read-tree docs for more information.
Now that we have a tree SHA, we can create a commit object that points to it. We can do this using the git commit-tree command. Most of the data that goes into the commit has to be set as environment variables, so you'll want to set the following:
GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE
Then you will need to write your commit message to a file or somehow pipe it into the command through STDIN. Then, you can create your commit object based on the tree sha we have.
$ git commit-tree bb2fa < /tmp/message a5f85ba5875917319471dfd98dfc636c1dc65650
If you want to specify one or more parent commits, simply add the shas on the command line with a '-p' option before each. The SHA of the new commit object will be returned via STDOUT.
Updating a Branch Ref
Now that we have a new commit object SHA, we can update a branch to point to it if we want to. Lets say we want to update our 'master' branch to point to the new commit we just created - we would use the git update-ref command:
$ git update-ref refs/heads/master a5f85ba5875917319471dfd98dfc636c1dc65650