skip to content

As a maintainer, how do you fetch and merge a contribution from someone's fork using Git alone?

level: seniorimportance: should knowfreq 34%

answer

  1. a fork is just another repository
  2. no special ref magic required
  3. where a URL fetch lands its tip
  4. two-dot versus three-dot for review
  5. fetch, topic branch, --no-ff merge

basics

~20 s

Fetch directly from the contributor's repository URL and branch, which lands in FETCH_HEAD, review and test it on a local topic branch, then merge it into your integration branch — typically with a no-fast-forward merge so the contribution stays visible as a unit.

solid answer

~50 s

A fork is just another repository, so no special tooling is needed. For a one-off, `git fetch https://example.com/ada/project.git fix-headers` leaves the tip in `FETCH_HEAD`; for a contributor you deal with repeatedly, `git remote add ada <url>` and `git fetch ada` gives you `refs/remotes/ada/*`. Review before you integrate: `git log --oneline main..FETCH_HEAD` for the series, `git diff main...FETCH_HEAD` for the net change against the merge base, and a topic branch (`git switch -c ada-fix-headers FETCH_HEAD`) to build and test it. Then integrate on your terms — `git merge --no-ff ada-fix-headers` keeps the series identifiable and records the integration point, `git merge --log` folds the commit subjects into the merge message, and a rebase or cherry-pick is the alternative when you want a linear history. Authorship is preserved either way; merging never rewrites who wrote the commits.

code

bash · 8 lines
bash
# one-off contribution
git fetch https://example.com/ada/project.git fix-headers
git log --oneline main..FETCH_HEAD
git diff main...FETCH_HEAD
git switch -c ada-fix-headers FETCH_HEAD
# build and test here
git switch main
git merge --no-ff --log ada-fix-headers

go deeper

for a junior

Recall that a fork is just another repository you can fetch from by URL, and that fetching only downloads — nothing in your branches changes until you merge.

for a middle

Explain where a URL fetch lands (FETCH_HEAD), why you move it to a topic branch before testing, and the difference between two-dot and three-dot ranges when reviewing the contribution.

for a senior

Demonstrate the integration judgment: no-fast-forward merge for a revertable, attributable unit versus rebase for linear history, and what each does to hashes, committer identity and the contributor's own branch.

for a principal

Own the policy: what your project's history should look like after a year of outside contributions, how provenance is recorded at integration time, and what you require of a contribution before it is fetched and built on your machines.

## A fork is not a special object The word "fork" describes a social arrangement, not a Git feature. The contributor's repository is an ordinary Git repository at a URL you can read. Everything a maintainer does with it uses the same fetch, review and merge commands as any other remote. Understanding that removes the mystery: you are never dependent on a particular hosting product's button to accept a contribution. ## Two ways to get the objects **Ad hoc.** `git fetch <url> <branch>` downloads the objects and writes the fetched tip into the special ref `FETCH_HEAD`. Nothing else in your repository changes; no branch is created and no local ref is updated. This is right for a one-time contribution: you do not want a permanent remote for someone who sends one patch. **Named remote.** `git remote add ada https://example.com/ada/project.git` followed by `git fetch ada` populates `refs/remotes/ada/*` and keeps it updatable. Worth it for a recurring contributor, or when you expect several rounds of review. `git remote remove ada` cleans up afterwards. Either way the fetch is read-only for the contributor: you are pulling from their repository, not pushing to it. ## Review before integrating Getting the objects is not accepting them. The reviewing steps that matter: - `git log --oneline main..FETCH_HEAD` — the commits they are proposing that you do not have. The two-dot range is exactly "theirs, not mine". - `git log -p main..FETCH_HEAD` — read the series commit by commit, which is how it was meant to be reviewed. - `git diff main...FETCH_HEAD` — the three-dot form diffs from the *merge base* to their tip, so it shows the net effect of their work without also showing everything that landed on `main` since they branched. Getting two-dot and three-dot the wrong way round here is a classic source of confusion. - `git switch -c ada-fix-headers FETCH_HEAD` — put it on a named local branch so you can build, test and poke at it. `FETCH_HEAD` is overwritten by the next fetch, so do not leave work depending on it. This is also where you check authorship and metadata: `git log --format='%h %an <%ae> %s'` shows whether the commits are attributed as expected and whether required trailers such as sign-off are present. ## Integrating Once satisfied, you have the usual choices, and the choice is a project-policy question: - `git merge --no-ff ada-fix-headers` creates a merge commit even when a fast-forward is possible. The contribution stays visible as a clustered group of commits with an explicit integration point that names who merged it and when; that merge commit is also the natural unit to revert. `--log` adds the merged commits' subjects to the merge message, and the merge message is where a maintainer records provenance. - A fast-forward (plain `git merge` when your branch has not moved) puts their commits directly on your branch with no merge commit — a linear history, but no record that these commits arrived together from outside. - `git rebase` or `git cherry-pick` replays their work onto your tip. This produces linear history but rewrites the commits: the *author* is preserved, while you become the committer and the hashes change. Contributors tracking their own branch will find it diverged. In all cases the author fields of the original commits survive; Git separates author from committer precisely so that integrating someone else's work does not steal credit. ## Asking for the pull in the first place On the contributor's side, `git request-pull <start> <url> <branch>` generates the classic summary that a maintainer receives in mailing-list workflows: the base commit, the URL and branch to fetch, a shortlog of the commits and a diffstat. It is a text message, not a network operation — it tells the maintainer exactly what to fetch and what they will get, and it fails loudly if the named branch is not actually present at that URL. ## Operational cautions Treat fetched code as untrusted input: you are about to build and run it. Test in the same isolation you would apply to any third-party code, and be aware that fetching alone does not execute anything, but checking out and building does. Also check what the series brings with it — a contributor who accidentally merged your `main` into their branch will drag a merge commit and possibly reordered history along; `git log --graph --oneline main..FETCH_HEAD` reveals that shape before you merge it.

  • Why review with git diff main...FETCH_HEAD rather than main..FETCH_HEAD?
    The three-dot form diffs from the merge base of the two to the contributor's tip, so it shows only their net change. The two-dot form diffs your tip against theirs, which also reports as "reversed" everything that landed on main since they branched — noise that has nothing to do with their contribution.
  • Where does git fetch <url> <branch> put what it downloads?
    In FETCH_HEAD, plus the objects in your object store. No branch is created and no remote-tracking ref is updated. FETCH_HEAD is overwritten by the next fetch, so create a local topic branch from it before doing any real work.
  • What does merging with --no-ff buy a maintainer that a fast-forward does not?
    An explicit integration commit. It records who merged the contribution and when, keeps the contributed commits grouped as an identifiable unit in the graph, and gives you a single commit to revert if the change turns out to be bad, rather than having to revert each commit individually.
  • Does merging or rebasing a contribution change who gets credit?
    No. A commit stores author and committer separately. Merging preserves both. Rebasing and cherry-picking preserve the author name, email and author date while making you the committer and creating new commit hashes, which is why the contributor's original branch then diverges from what landed.

saying these in an interview costs you the question

  • Thinks accepting a fork contribution requires the hosting platform
  • Believes merging makes the maintainer the author
  • Leaves work sitting on FETCH_HEAD across fetches
  • Reviews with a two-dot diff and blames the contributor for the noise
  • Assumes a fast-forward preserves the contribution as a visible unit

context