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.
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