Standalone Git branch from subdirectory
Imagine you have a subdirectory of generated files and you want to store
them into Git as a standalone (orphan) branch. For example, you have
generated html files and associated CSS and JavaScript assets, with the
intention of publishing them as a GitHub Pages
site
from the gh-pages
Git branch of the project. Git’s plumbing commands
allow automating storing the generated files into the gh-pages
branch,
recreating the branch each time you publish. Here’s a Bash oneliner to
do that:
For that Bash command list, I presume that
- the generated files are already in the
build
subdirectory, - you include
build
in the.gitignore
file of the project, and - the Git index (staging area) is clear currently.
Let’s go over the parts of the command list.
1. Stage the generated files into Git index
I’ll use the --force
switch to allow Git to add ignored files.
2. Create a Git tree object from the current index
A Git tree object groups Git objects and stores the paths of the objects. The tree object will be used to create a commit object in the next step.
The --prefix=build/
option makes Git to treat the build
directory as
the root directory for the files within the directory. For example, a
file with the build/dir/index.html
path gets recorded with the
dir/index.html
path inside the tree object.
The command prints the name of the tree object to stdout (I’ll use the
$tree
shell variable for that in the next step).
3. Create a Git commit object from the tree object
The command prints the commit object id to stdout (let’s put it into the
$commit
variable).
4. Set a branch to refer to the commit object
This overwrites the gh-pages
branch, if it exists already.
5. Reset index to the current HEAD
This needs to be done for clearing the index.
Now the target branch, gh-pages
, contains a single orphaned commit,
using the build
subdirectory as the root directory of the files.
If you want to, you can store the previous commit of the gh-pages
branch as the parent of the next commit, but then there are edge cases
to consider: you’ll need to detect if the target branch exists already
and whether the new contents of the build
directory differ between the
next and the previous commit (it doesn’t make sense to create a new
commit with an empty diff compared to the parent commit). Covering them
would require elaborate scripting compared to the Bash oneliner I went
through.
As a real example, the Hacker’s Tiny Slide
Deck project uses
this trick in storing the generated slides (an html file) and the
JavaScript bundle of the project into the gh-pages
branch of the
project, from where GitHub Pages publishes them. The relevant Git
commands are in
package.json.
Here’s a screenshot of GitUp app’s map view of the
Git repository of Hacker’s Tiny Slide Deck, showing what the standalone
gh-pages
branch looks like:
The chapter titled Git Objects from the Pro Git book is a great resource for learning more about Git internals.