Skip to content
ExponaBLOG
GIT & GITHUB FOR PROFESSIONAL DEVELOPERS · DAY 080 / 100

Git for non-code projects: practical tips and trade-offs

Version anything text-based.

Expona Admin7 min read

Most teams think of Git as a tool for source code, but we've found that versioning non-code assets—design tokens, infrastructure definitions, documentation, and data files—brings the same benefits to the entire organization. Git for non-code projects: practical tips and trade-offs means treating these artifacts with the same rigor we apply to production code, complete with commit history, code review, and reproducibility.

This guide walks through real patterns we've used across e-commerce, SaaS, and infrastructure teams to version anything text-based, and highlights the trade-offs you'll encounter along the way.

Design tokens and configuration

Design tokens—colors, typography scales, spacing systems, border radii—are the bridge between designers and developers. Historically, these live in Figma, a design tool, or in scattered Slack messages. The moment you version them in Git, your entire team operates from a single source of truth, and every change is auditable.

We typically store tokens in JSON or YAML format. Here's a practical structure:

bash
tokens/
├── color.json
├── typography.json
├── spacing.json
└── semantic.json

A color.json might look like this:

bash
{
  "color": {
    "primary": {
      "50": "#f0f9ff",
      "500": "#3b82f6",
      "900": "#1e3a8a"
    },
    "neutral": {
      "50": "#fafafa",
      "900": "#0a0a0a"
    }
  }
}

The trade-off: Figma remains the source of truth for designers, and tokens in Git must be manually exported or synced via a CI/CD pipeline. If you're running a smaller design system, this overhead might outweigh the benefit. For teams shipping multiple products or platforms, it's essential. We've seen teams use GitHub Actions to sync Figma tokens to a repository on every design system update, then automatically generate platform-specific files (CSS variables, iOS constants, Android resources) from a single JSON source.

Configuration files—API endpoints, feature flags, environment variables for different stages—belong in Git too, but with discipline. Secrets should never be committed; use environment variable management or a secrets manager. Public configuration lives alongside the code that consumes it.

Infrastructure as code

Infrastructure as code (IaC) is perhaps the most mature use case for Git outside of application code. Terraform, CloudFormation, CDK, and Pulumi all produce text-based definitions that describe your cloud resources, and versioning them in Git gives you the audit trail and rollback capability you'd want in production.

A typical IaC repository structure:

bash
infrastructure/
├── terraform/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   ├── environments/
│   │   ├── dev.tfvars
│   │   ├── staging.tfvars
│   │   └── production.tfvars
│   └── modules/
│       ├── networking/
│       ├── compute/
│       └── database/
├── docs/
│   └── architecture.md
└── .gitignore

The .gitignore for Terraform is critical:

bash
# Terraform
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl.* (or keep if team agrees)
crash.log
override.tf.json
*_override.tf.json

When a team member proposes infrastructure changes, they open a pull request. CI/CD runs terraform plan to show the diff of what will change. This is infinitely better than someone logging into the AWS console and clicking buttons. The trade-off: it adds complexity to deployment pipelines, and the cost of mistakes—accidentally deleting a database—is high. Mitigation comes from restricting who can merge, using approval gates, and running terraform plan on every PR so reviewers see the actual impact before the change is deployed.

Writing and documentation

Documentation as code is underrated. READMEs, architecture decision records (ADRs), runbooks, and knowledge base articles all benefit from version control. You get attribution, history, and the ability to discuss changes before they go live.

A straightforward setup:

bash
docs/
├── README.md
├── architecture/
│   ├── adr-001-database-choice.md
│   ├── adr-002-caching-strategy.md
│   └── system-overview.md
├── runbooks/
│   ├── incident-response.md
│   ├── database-backup.md
│   └── scaling-procedures.md
└── guides/
    ├── onboarding.md
    └── deployment.md

An ADR template is valuable here. It forces teams to record decisions, trade-offs, and consequences. Example:

bash
# ADR-001: Choosing PostgreSQL over MongoDB

## Status
Accepted

## Context
We need a primary database for our order management system. Team split between SQL and NoSQL advocates.

## Decision
We chose PostgreSQL.

## Consequences
- Strong ACID guarantees reduce complexity in transaction handling
- Schema evolution requires migrations (not automatic)
- JSON support available if semi-structured data needed

The benefit here is massive for onboarding and auditing. New engineers can read the reasoning behind key choices. When a decision is revisited, the old ADR is linked in the new PR, keeping context alive.

The trade-off: documentation goes stale quickly if no one owns it. Unlike code, which breaks loudly when untested, outdated docs fail silently. Enforce code reviews on documentation PRs just as strictly as code. Some teams use GitHub branch protection rules to require at least one approval on any changes to the docs directory.

Data files with clear diffs

CSV, JSON, and YAML data files are often candidates for Git. Think product catalogs, feature flags, experiment configs, localization strings, or test fixtures. The key is choosing a format that produces readable diffs.

CSV is human-readable but produces noisy diffs when columns are reordered. JSON is compact but hard to diff line-by-line. YAML is readable and diffs reasonably well:

bash
features:
  - name: new-checkout-flow
    enabled: true
    rolloutPercentage: 100
    targetRegions:
      - us-east-1
      - eu-west-1
  - name: one-click-reorder
    enabled: false
    rolloutPercentage: 0

When someone changes rolloutPercentage for one feature, the diff is clear and reviewable. For product catalogs or large datasets, JSON Lines format (one JSON object per line) sometimes strikes a good balance:

bash
{"id": 1, "sku": "WIDGET-001", "price": 29.99, "stock": 150}
{"id": 2, "sku": "WIDGET-002", "price": 39.99, "stock": 87}

Adding or changing a single product creates a one-line diff. Removing a line is obvious.

The trade-off: data files in Git scale poorly if they're large or frequently changing. A CSV with 100,000 rows will bloat your repository. If the data changes multiple times per hour, you'll accumulate merge conflicts. For truly dynamic data (real-time inventory, user profiles), Git is the wrong tool—use a database. Reserve Git for reference data, configuration, and things that change on a weekly or monthly cadence.

Common mistakes

  • Committing secrets in non-code files. API keys, database passwords, and OAuth tokens can hide in environment files, configuration JSON, or even Terraform state. Use .gitignore, environment variable management, and pre-commit hooks to prevent this. Tools like git-secrets or pre-commit framework can scan commits before they're pushed.
  • Treating Git as a data dump. If you're versioning large CSV exports or binary assets, Git will become slow and bloated. Separate code repos from data repos, or use Git LFS (Large File Storage) for heavy files, but understand that LFS requires additional setup and isn't ideal for collaboration.
  • Ignoring merge conflicts in configuration. When two team members edit the same configuration file in parallel, Git can't auto-merge. Establish clear ownership (only one person edits feature flags, for example) or use feature flag services that provide real-time UI-based management instead of files.
  • Over-versioning intermediate outputs. If a file is generated from another file in the repo, don't version the output. Version the source and regenerate during build or deploy. This eliminates unnecessary commits and keeps diffs focused.
  • Forgetting to document manual steps. When infrastructure is in Git but the deployment process is "run this Terraform command by hand," you've missed the benefit. Automate the deployment itself via CI/CD so the repository is the single source of truth for what will be deployed.

Key takeaways

  • Anything text-based and collaborative—design tokens, infrastructure definitions, documentation, configuration—belongs in version control alongside your code.
  • Choose file formats that produce readable diffs. YAML and JSON Lines generally work better than CSV for this reason.
  • Never commit secrets; use environment variables or a secrets manager. Enforce this with pre-commit hooks and CI checks.
  • Automation is essential. Sync design tokens from Figma, run Terraform plan on PRs, and generate platform-specific outputs from a single source.
  • Data files scale poorly in Git if they're large or change frequently. Reserve Git for reference data and configs; use databases for transactional or real-time data.
  • Documentation in Git requires active ownership. Set the same code review standards for docs as you do for code to keep them current.
  • Infrastructure as code is the most mature and critical use case; treat every infrastructure change as a PR that's reviewed and approved before deployment.
  • Trade-offs are real: versioning non-code adds overhead and complexity. Justify each use case by the value it brings (auditability, reproducibility, team alignment) against the cost of maintenance.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles