# Git: from file to content

> **Note**
>
> Are you used to handling files? Git invites you to think differently. The advantage: you have much greater control over your content.
>
> This article explains how Git stores content, how to [use branches to work around validation bottlenecks](#overcoming-documentation-bottlenecks-with-git-branches), and how to [clean up your history with git rebase](#organizing-your-history-with-git-rebase) before sharing it.

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

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 |
| *Commit* 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](https://docs.redaction-technique.org/en/tech-writing-process/using-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 branch 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 repository.

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

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:

```bash
git checkout -b my-branch
git add my-files
git 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

Return to the *master* branch:

```bash
git checkout master
```

Your workspace now reflects *master*, without Task 1. Task 1 remains on its branch while Task 2 progresses on *master*:

> **Diagram: Task 1 parked on its branch while Task 2 progresses on master**
>
> Git history with two branches: master points to commit A, with Task 2 in progress in the workspace; my-branch forks from commit A and points to commit B, where Task 1 is committed.

### Resume or integrate the first task

The next action depends on the state of Task 1.

#### 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:

```bash
git checkout my-branch

# make changes

git add my-files
git 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](#organizing-your-history-with-git-rebase)).

> **Tip: If Git refuses to switch branches or to merge**
>
> Git stops with `Your local changes to the following files would be overwritten by checkout` (or `by merge`) and lists the files concerned. Nothing has been changed yet: *commit* or stash your changes, then run the command again.

#### Task 1 is validated

Merge *my-branch* into *master*. If Task 2 has uncommitted changes, put them aside first:

```bash
git stash
git merge my-branch
git stash pop
```

The 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:

> **Diagram: master after merging Task 1**
>
> Git history of the master branch: commit A followed by commit B, where Task 1 is now integrated.

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

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:

```bash
git worktree add ../task-1 my-branch
```

This 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:

```text
project/   master     Task 2
task-1/    my-branch  Task 1
```

You 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:

```bash
cd project
git merge my-branch
```

The *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`.

> **Tip**
>
> Use a branch when you need to isolate a task. Use `git worktree` when you need to work on two branches simultaneously in separate directories.

### 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:

```bash
git restore .
```

or, for a single file:

```bash
git restore path/to/my-file
```

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

> **Caution**
>
> `git reset --hard HEAD` is more destructive: it resets both the index and the working tree to `HEAD`. It can permanently discard all uncommitted changes to tracked files, staged or not.

## 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:

> **Diagram: Interleaved commit history before rebase**
>
> Git history showing five commits alternating between text correction and synopsis changes, before reorganizing them with rebase.

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

> **Caution**
>
> The *rebase* command is potentially destructive; ensure you back up your workspace, including the *.git* directory, before running it, or risk losing data. Because *rebase* rewrites history, only reorganize *commits* you haven't shared yet: rebasing history that others have already retrieved forces them to reconcile diverging versions.

To reorganize your *commits*:

1. Create a temporary backup branch, so that you can return to the current history at any time: `git branch backup`.
2. Run `git rebase -i HEAD~5` to reorganize your *commits* from the current version to the previous five, for example.
3. In the editor that opens, reorder the lines to group the *commits* by task, and replace `pick` with `squash` on each *commit* to merge into the one above it.
4. 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:

> **Diagram: Grouped commit history after rebase**
>
> Git history showing two commits after rebase, one grouping all text correction changes and one grouping all synopsis changes.

**Git history after rebase**

The *commits* are first grouped by type on Git’s *time arrow*, then merged.

> **Tip: If the rebase goes wrong**
>
> While the rebase is in progress, for example when it stops on a conflict, `git rebase --abort` restores the history as it was before. Once it is finished, `git reset --hard backup` returns your branch to the backup branch; like any `git reset --hard`, it discards uncommitted changes.

> **Note**
>
> If you've performed both tasks simultaneously on one or more files, don't panic. With the `git add -p` command, you can distribute your nested modifications over the appropriate *commits*. When you run `git status`, you'll see your files are both ready and not ready to be *committed*: two states of the files represent partial stages of your work, summing up to the total modifications you've made.

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](https://docs.redaction-technique.org/en/tech-writing-process/integrating-documentation-into-development/). 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.

> **Caution**
>
> The less you reorganize your *commits* (especially chronologically), the lower the risk of having to manually resolve conflicts. In other words, `git rebase` should not be an excuse for unplanned work.

## Related articles

- [Version management systems - rustic but reliable](https://docs.redaction-technique.org/en/tech-writing-process/version-control-systems/)
- [Repository](https://docs.redaction-technique.org/en/tech-writing-process/repository/)
- [A single repository?](https://docs.redaction-technique.org/en/tech-writing-process/single-repository/)
- [Example: a documentation repository structure](https://docs.redaction-technique.org/en/toolkit/example-repository-structure/)

## Related reading

- [Manage content in files, not databases](https://redaction-technique.org/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](https://redaction-technique.org/web-journey-html-to-git-markdown) — moving content into a Git + Markdown workflow.

---

Source: https://docs.redaction-technique.org/en/tech-writing-process/git-from-file-to-content/
