Git: from file to content
Think content rather than files with Git: branches, commits, and diffs for technical documentation make collaboration and version management far simpler and safer.
Definition
Section titled “Definition”What is a file? For you, it’s a piece of content - an image, text, spreadsheet, or other - identified by a name. For your operating system, it’s a sequence of bits on the hard disk with an associated file name and directory path. If you want to manage your project in terms of files in Git, you’re in for trouble. If you think in terms of content, everything becomes much simpler.
Key principles
Section titled “Key principles”When you provide Git with a file, it decomposes it into two components:
- a content (sequence of bits, or blob),
- a tree (link between filename and content).
Git stores these components in two areas, the index (temporary area) and the object database (persistent zone):
| Action | Index | Object database |
|---|---|---|
Add a file (git add <file>) | Tree | Content |
CommitA recorded snapshot of changes in a Git repository, with an author, a date, and a message explaining the change. Commits form the project history that branches start from and that reviewers and translators compare.View in glossary → the file (git commit) | - | Tree, stored permanently |
Git does not directly compare two files. It compares their checksums, which are single numbers calculated from their contents. If the checksums of two files are identical, their contents are identical (to the nearest bit).
The history of your project is not necessarily linear: you can have it follow several parallel paths, or branches.
You can only create branches from a commit. Think of commits as roundabouts (with the road being your project’s history) from which you can, if you wish, take your project in a different direction.
A BranchA parallel line of work in a version-control system: changes made on a branch stay isolated until they are reviewed and merged back into the main line, so several writers, releases or languages can progress at the same time.View in glossary → only contains commits. If you create a branch, let’s say test, while your workspace contains uncommitted changes, these changes are not moved to test. They remain in your workspace and follow you when you switch branches. Only a commit adds them to a branch’s history, so switching branches may require you to commit or stash your changes first. If you make a mistake, you won’t be able to restore the status quo ante of your files by returning to the master branch.
To save your work progressively, allowing you to return to a previous state at any time, you need to commit regularly and back up your workspace, including the .git directory, e.g., using rsync. When you decide to share your work, you can move, merge, or delete your commits before sending them as patches or storing them in a central RepositoryThe place where a documentation team keeps its sources: shared folders, a CMS database, or a version-control system such as Git, which also stores the complete history of every file.View in glossary →.
Overcoming documentation bottlenecks with Git branches
Section titled “Overcoming documentation bottlenecks with Git branches”Git branches make it easy to perform several unrelated tasks in parallel.
Let’s imagine the following work scenario:
- You are asked to migrate a section from one document to another (Task 1).
- You send your proposal for validation.
- Validation is slow, and you have to move on to other parts of the documents (Task 2).
Rather than following a long sequence of commands, think in terms of situations: at each step, ask yourself where the work lives and what state it is in.
Park the first task
Section titled “Park the first task”You are on the master branch. Your workspace contains Task 1 modifications that you don’t want to commit to master before validation. Create a branch from the current commit, then commit Task 1 to it:
git checkout -b my-branchgit add my-filesgit commit -m "my commit message"Creating the branch alone does not isolate anything: your uncommitted changes stay in the workspace. It is the commit that stores Task 1 in the history of my-branch. Task 1 is now safely stored in my-branch.
Work on another task
Section titled “Work on another task”Return to the master branch:
git checkout masterYour workspace now reflects master, without Task 1. Task 1 remains on its branch while Task 2 progresses on master:
Resume or integrate the first task
Section titled “Resume or integrate the first task”The next action depends on the state of Task 1.
Task 1 needs changes
Section titled “Task 1 needs changes”If Task 2 has uncommitted changes, set them aside first with git stash. Otherwise, they follow you to my-branch, or Git refuses to switch.
Then return to the branch, make the requested changes, and commit them:
git checkout my-branch
# make changes
git add my-filesgit commit -m "address validation feedback"Repeat as often as necessary, then return to master with git checkout master, and retrieve Task 2 with git stash pop if you set it aside. You can merge these commits into a single one after validation (see Organizing your history with git rebase).
Task 1 is validated
Section titled “Task 1 is validated”Merge my-branch into master. If Task 2 has uncommitted changes, put them aside first:
git stashgit merge my-branchgit stash popThe stash is needed because Task 2 exists only in your workspace, not in a commit. Git refuses to merge if the merge would overwrite files you are modifying, and mixing a merge with work in progress makes both harder to check. git stash gives you a clean workspace for the merge, and git stash pop puts Task 2 back in the workspace, on top of the result:
If git stash pop reports a conflict, Task 1 and Task 2 changed the same lines. Resolve the conflict in the files listed, then delete the stash entry, which Git keeps in case you need it again, with git stash drop.
Keep this distinction in mind: the branch contains the history of the task, while the workspace contains whatever you are currently editing.
Working on two branches simultaneously
Section titled “Working on two branches simultaneously”Switching branches in a single workspace is fine when you move from one task to the other occasionally. If you genuinely need to work on two branches simultaneously, use git worktree, which associates an additional working directory with a branch of the same repository.
From project/, your main working directory, run:
git worktree add ../task-1 my-branchThis creates task-1/, a separate working directory next to project/, with my-branch checked out. You now have two sibling directories, each associated with its own branch and its own work in progress:
project/ master Task 2task-1/ my-branch Task 1You edit and commit Task 1 in task-1/, and Task 2 in project/. There is no need to stash Task 2 simply to switch branches: project/ stays on master, and you change directories instead.
When Task 1 is validated, merge it from the main working directory:
cd projectgit merge my-branchThe commits made in task-1/ are already visible from project/, as both directories share the same repository. If the merge touches files that Task 2 is still modifying, commit or stash Task 2 first. You can then delete the additional working directory with git worktree remove ../task-1.
Discarding uncommitted changes
Section titled “Discarding uncommitted changes”If you don’t need to run two tasks in parallel, you can simply work in your local workspace. To discard your uncommitted changes to tracked files, restore them from Git:
git restore .or, for a single file:
git restore path/to/my-fileWithout --staged, these commands only discard unstaged changes to the specified tracked files: changes already staged with git add are kept, and untracked files are not affected.
Organizing your history with git rebase
Section titled “Organizing your history with git rebase”Git can be confusing at first due to its focus on content rather than files. However, this approach simplifies group work and the management of different concurrent versions of the same content.
Git performs atomic commits. It applies batches of modifications to content across multiple files, rather than managing individual files. This encourages thinking in terms of batches of tasks on content.
This may not seem intuitive if you’re used to working file by file rather than task by task. But once you’ve adapted your work habits to this workflow, you’ll see:
- you have a history that is more effectively utilized,
- it’s much easier to manage concurrent versions of the same content in parallel development branches.
Suppose you’ve identified two major types of changes to be made to your content:
- command-line program synopses,
- grammatical corrections to text.
If your content is divided into a set of modular files, you might decide to make both types of changes in each file simultaneously. To distribute the work among a group of technical writers, simply allocate a batch of files to each of them.
This workflow isn’t ideal for Git. Instead, divide the work into two batches of tasks, called synopsis and text, applied concurrently to all files.
Production constraints may force you to further split these batches into sub-batches, which you’ll have to alternate between.
You commit each sub-batch upon completion. Your commit history will then resemble the following diagram:
Git history before rebase
When placing your commits on the central repository, some will represent interim steps in a task. Your history and branches may be harder to manage as unfinished tasks alternate. To retrieve a single task, you’ll need to carefully select the commits using the git cherry-pick command.
Fortunately, Git facilitates reorganizing your commits before sharing them.
To reorganize your commits:
- Create a temporary backup branch, so that you can return to the current history at any time:
git branch backup. - Run
git rebase -i HEAD~5to reorganize your commits from the current version to the previous five, for example. - In the editor that opens, reorder the lines to group the commits by task, and replace
pickwithsquashon each commit to merge into the one above it. - Save and close the editor. Git rewrites the history, then asks you to edit the messages of the merged commits.
You can then offer your collaborators a commit for each completed task, as shown in the following diagram:
Git history after rebase
The commits are first grouped by type on Git’s time arrow, then merged.
By using this approach, you lose access to intermediate commits, but this is intentional: each unique commit represents a consistent state of your content.
This workflow also facilitates teamwork: you can assign these tasks to two different team members, each working in their own local space - an approach that supports integrating documentation into development processes. The former’s changes are then merged with the latter’s in their local space via patches. Finally, the commits are refactored before being placed in the central repository.
Related articles
Section titled “Related articles”- Version management systems - rustic but reliable
- Repository
- A single repository?
- Example: a documentation repository structure
Related reading
Section titled “Related reading”- Manage content in files, not databases - why file-based content beats a database for documentation.
- A web journey: from HTML to Git-based Markdown workflows - moving content into a Git + MarkdownA lightweight plain-text markup language in which formatting is written with simple punctuation, such as a leading # for a heading. It is easy to write and to review under version control, but has no schema to enforce structure, so docs-as-code teams rely on templates and automated checks instead. This manual is written in MDX, a Markdown variant.View in glossary → workflow.