---
title: "04 — Version Control and Repo Hosting — Git the Engine, GitHub the Collaboration"
uid: version-control-and-hosting
tags: ["devops", "github", "collaboration", "gitlab", "roadmap:backend", "version-control", "git"]
excerpt: "Git is the engine — a local data structure of snapshots. GitHub, GitLab, Bitbucket are the collaboration layer bolted on top. Conflating them blocks understanding both."
date: 2026-08-13T03:28:27+0000
source: https://www.aveshina.my.id/en/blog/version-control-and-hosting
---

"Just push to GitHub" was the whole of my version-control model, and it never explained a single confusing Git error. Separating two things I'd fused is what did: **Git is the engine, a local data structure that records snapshots of your code; GitHub, GitLab, and Bitbucket are the collaboration layer bolted on top.** [1][5] Conflating them is the single biggest blocker to actually understanding either.

The frame that fixed it is ownership. Git runs on my laptop. It's a command-line program that writes to a hidden .git directory — a local database of every commit, branch, and tag I've made. I can use Git for a project no one else ever sees, with no network and no account. The hosting platform is a separate product: a website that stores a copy of my Git repository and adds a UI for humans to coordinate around it — pull requests, issues, code review, CI. The platform depends on Git; Git does not depend on the platform.

```figure
<svg viewBox="0 0 740 320" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Two layers. Top layer: three hosting platform boxes (GitHub, GitLab, Bitbucket) inside a rounded container labelled 'the collaboration layer — pull requests, issues, CI'. Bottom layer: a local Git DAG — five commit circles connected by arrows, with two branch labels (main, feature) — inside a container labelled 'your machine — the Git engine'. A vertical double-arrow labelled 'git push / git fetch' connects the two layers.">
  <defs>
    <marker id="vcarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- top: hosting platforms -->
    <rect x="60" y="30" width="620" height="90" rx="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="370" y="52" font-size="12" font-weight="700" fill="#500724" text-anchor="middle">the collaboration layer — pull requests, issues, code review, CI</text>
    <g>
      <rect x="120" y="65" width="120" height="40" rx="8" fill="#ffffff" stroke="#db2777" stroke-width="1.5"/>
      <text x="180" y="90" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">GitHub</text>
      <rect x="310" y="65" width="120" height="40" rx="8" fill="#ffffff" stroke="#db2777" stroke-width="1.5"/>
      <text x="370" y="90" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">GitLab</text>
      <rect x="500" y="65" width="120" height="40" rx="8" fill="#ffffff" stroke="#db2777" stroke-width="1.5"/>
      <text x="560" y="90" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">Bitbucket</text>
    </g>

    <!-- vertical arrows -->
    <line x1="350" y1="135" x2="350" y2="170" stroke="#64748b" stroke-width="1.5" marker-end="url(#vcarrow)"/>
    <line x1="390" y1="170" x2="390" y2="135" stroke="#64748b" stroke-width="1.5" marker-end="url(#vcarrow)"/>
    <text x="415" y="158" font-size="10" fill="#475569">push / fetch</text>

    <!-- bottom: local git DAG -->
    <rect x="60" y="180" width="620" height="120" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="370" y="202" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">your machine — the Git engine (a local DAG of snapshots)</text>

    <!-- commits -->
    <g fill="#ffffff" stroke="#6366f1" stroke-width="2">
      <circle cx="130" cy="250" r="14"/>
      <circle cx="210" cy="250" r="14"/>
      <circle cx="290" cy="250" r="14"/>
      <circle cx="370" cy="250" r="14"/>
      <circle cx="470" cy="250" r="14"/>
      <circle cx="570" cy="250" r="14"/>
    </g>
    <!-- arrows between commits -->
    <g stroke="#64748b" stroke-width="1.5" fill="none">
      <line x1="216" y1="250" x2="204" y2="250" marker-end="url(#vcarrow)"/>
      <line x1="296" y1="250" x2="284" y2="250" marker-end="url(#vcarrow)"/>
      <line x1="376" y1="250" x2="364" y2="250" marker-end="url(#vcarrow)"/>
      <line x1="466" y1="248" x2="384" y2="248" marker-end="url(#vcarrow)"/>
      <line x1="566" y1="250" x2="484" y2="250" marker-end="url(#vcarrow)"/>
    </g>
    <!-- branch labels -->
    <text x="590" y="245" font-size="11" font-weight="700" fill="#16a34a">feature</text>
    <text x="290" y="285" font-size="11" font-weight="700" fill="#6366f1">main</text>
  </g>
</svg>
```

## Git: snapshots, not diffs

The single idea that made Git click for me: **a commit is a snapshot of your entire project at a moment, not a diff against the previous version.** [2] Internally Git stores a full tree of files per commit; the diffs are computed on demand for efficiency. This is why branching is cheap in Git — a branch is just a movable pointer to a commit, and creating one doesn't copy any files.

A few mechanics I had to nail down:

- **The three areas.** Working directory (what I see), staging area / index (what I've marked for the next commit), and the repository (the committed history). git add moves from working to staging; git commit moves from staging to history.
- **Branches are pointers.** A branch name points at one commit. HEAD points at the branch I'm currently on. Switching branches moves HEAD and updates my working directory.
- **History is a DAG.** Commits form a directed acyclic graph. A merge commit has two parents. This is why Git history can be non-linear and why rebasing (rewriting commits onto another base) is possible but rewrites history.

The everyday commands are a small surface: status, add, commit, log, branch, checkout/switch, merge, push, pull, fetch. The power commands (rebase, cherry-pick, reflog, reset) come later, and they're all operations on the same snapshot DAG.

## Distributed: every clone is a full repository

Git is **distributed** — every clone is a full-fledged repository with complete history [2]. I can commit, branch, and merge with no network connection. The git push and git fetch commands move commits between repositories (typically mine and a hosted one), but the local repository is autonomous.

This is the part the central-platform way of thinking hides. When I git push origin main, I'm sending my new commits to another repository that happens to live on GitHub's servers. When I git fetch, I'm pulling theirs down. The platform is a peer in this exchange — a very visible peer, but architecturally just another repository. If GitHub disappeared tomorrow, my local .git directory would still contain my entire project history.

## The hosting platforms: collaboration on top of the engine

**GitHub** is the dominant hosting platform — Microsoft-owned, with repositories, pull requests, issues, GitHub Actions for CI, and a near-monopoly on open-source visibility [3]. If I'm reading or contributing to public open source, it's almost certainly on GitHub. The pull request (PR) is GitHub's killer feature: a proposed change from one branch, with diff view, line-by-line review, and merge controls. PRs are GitHub's UI, not Git's — Git has no concept of a pull request.

**GitLab** is the closest competitor and the one that differentiates by being **all-in-one DevOps** — source control, CI/CD, issue tracking, container registry, and security scanning in a single application [4]. GitLab is also notable for offering a self-hosted option (the open-source GitLab CE/EE), which matters for organizations that can't or won't put their code on someone else's servers. For shops that want one platform for the whole development lifecycle, GitLab's integration is a real advantage.

**Bitbucket** (Atlassian) is the third major option, tightly integrated with Jira and the Atlassian suite, and a common choice in enterprise shops already on Atlassian tooling.

The choice between them is rarely about Git — they all speak Git fluently. It's about the collaboration features, the CI integration, the issue tracker, the hosting model (cloud vs self-hosted), and where the team already is.

## Pull requests: where the platform earns its keep

The pull request is the workflow feature that makes the hosting layer more than storage. The pattern:

1. I create a feature branch off main and commit my changes locally.
2. I git push the branch to the hosted repository.
3. I open a pull request on the platform — a page showing my branch's diff against main, with space for review comments.
4. Reviewers comment; I push more commits to the same branch (the PR updates automatically).
5. When approved, the platform merges the branch into main (or via a squash/rebase merge).

Steps 3–5 are platform features, not Git features. The same merge could be done locally with git merge, but the PR adds the human review and CI gating that make team development workable. CI pipelines (GitHub Actions, GitLab CI) typically run on every PR, blocking merge until tests pass.

## How I use this

The separation of engine from platform shapes how I work day to day:

- **Commit locally, often.** Commits are free and local. I commit small, logical units as I go, before pushing anything. The DAG is mine to shape with rebase before anyone else sees it.
- **Treat the platform as the coordination point.** PRs are where review happens; I don't push directly to main on anything with more than one contributor.
- **Pick the platform by the team and the workflow, not by Git.** Open source → GitHub. Self-hosted full DevOps → GitLab. Atlassian shop → Bitbucket. The Git underneath is identical.
- **Learn Git itself, not just the platform UI.** When a PR has conflicts, the resolution happens in Git, locally. The reflog has saved me more than once from mistakes the platform UI can't undo.

The habit that matters most: when something breaks, I ask whether it's a Git problem (the engine, the local DAG) or a platform problem (the PR, the CI, the permissions). They fail in different ways and need different fixes.

## References

[1] Atlassian, "What is version control?," 2024. [Online]. Available: [https://www.atlassian.com/git/tutorials/what-is-version-control](https://www.atlassian.com/git/tutorials/what-is-version-control)

[2] "Tutorial: Git for Absolutely Everyone," The New Stack. [Online]. Available: [https://thenewstack.io/tutorial-git-for-absolutely-everyone/](https://thenewstack.io/tutorial-git-for-absolutely-everyone/)

[3] GitHub, "GitHub Documentation." [Online]. Available: [https://docs.github.com](https://docs.github.com)

[4] GitLab, "GitLab Documentation." [Online]. Available: [https://docs.gitlab.com/](https://docs.gitlab.com/)

[5] roadmap.sh, "Repo Hosting Services — Backend Roadmap." [Online]. Available: [https://roadmap.sh/backend/repo-hosting-services](https://roadmap.sh/backend/repo-hosting-services)

```quiz
Q: What is the relationship between Git and GitHub?
- They are the same thing, just different names
- Git is a local version-control engine; GitHub is a hosting platform that adds collaboration features on top of Git
correct: 1
explain: Git is the engine — a local program that records snapshots. GitHub (and GitLab, Bitbucket) are websites that host Git repositories and add pull requests, issues, and CI on top. Conflating them blocks understanding both.

Q: Internally, what does a Git commit actually store?
- A diff against the previous commit
- A full snapshot of the entire project tree at that moment
correct: 1
explain: A commit stores a snapshot of the whole project, not a diff. Diffs are computed on demand. This is why branching is cheap — a branch is just a pointer to a commit, with no file copying.

Q: Which best describes the pull request workflow?
- A Git feature for merging branches locally
- A platform feature: push a branch, open a reviewable diff on the hosting site, gate the merge on review and CI
correct: 1
explain: Pull requests are a GitHub/GitLab platform feature, not a Git concept. Git can merge branches locally; the PR adds the human review and CI gating that make team coordination workable.

Q: Why is Git described as "distributed"?
- Every clone is a full repository with complete history, usable offline
- It requires a central server to function
correct: 0
explain: Every clone is autonomous — full history, full branching, full merging, all offline. Push and fetch move commits between peers (often a hosted copy), but the local repository needs no network to function.

Q: What distinguishes GitLab from GitHub in positioning?
- GitLab offers an all-in-one DevOps platform (CI/CD, registry, issues) and a self-hosted option
- GitLab only hosts Git repositories and nothing else
correct: 0
explain: GitLab's differentiator is integrating the whole DevOps lifecycle (source, CI, issues, registry, security) in one application, plus a self-hostable open-source edition. GitHub is more dominant for open-source visibility but historically narrower in scope.
```
