pcottle/learnGitBranching · error · GitError

git-error-origin-fetch-no-ff

Error message

git-error-origin-fetch-no-ff

What it means

Thrown by GitEngine's fetch/push fast-forward check when the destination branch's current commit is not an ancestor of the source branch's commits. In real git this is the 'fetch first' / non-fast-forward rejection: the remote (or target) has commits that the local source does not contain, so updating the ref would lose history.

Source

Thrown at src/js/git/index.js:1030

 * Origin stuff!
 ************************************/

GitEngine.prototype.checkUpstreamOfSource = function(
  target,
  source,
  targetBranch,
  sourceBranch,
  errorMsg
) {
  // here we are downloading some X number of commits from source onto
  // target. Hence target should be strictly upstream of source

  // lets first get the upstream set from source's dest branch
  var upstream = Graph.getUpstreamSet(source, sourceBranch);

  var targetLocationID = target.getCommitFromRef(targetBranch).get('id');
  if (!upstream[targetLocationID]) {
    throw new GitError({
      msg: errorMsg || intl.str('git-error-origin-fetch-no-ff')
    });
  }
};

GitEngine.prototype.getTargetGraphDifference = function(
  target,
  source,
  targetBranch,
  sourceBranch,
  options
) {
  options = options || {};
  sourceBranch = source.resolveID(sourceBranch);

  var targetSet = Graph.getUpstreamSet(target, targetBranch);
  var sourceStartCommit = source.getCommitFromRef(sourceBranch);

View on GitHub (pinned to 5b09d0ff96)

Solutions

  1. Pull or fetch and merge/rebase first so your source contains the target's commit
  2. Push to a different destination branch or reset it to match before pushing
  3. As a library consumer, pass the force/override option the surrounding API supports if history rewrite is intended

Example fix

// before
this.push('main', 'main'); // remote main diverged
// after
this.pull('main', 'main');
this.push('main', 'main');
Defensive patterns

Strategy: validation

Validate before calling

var upstream = Graph.getUpstreamSet(source, sourceBranch);
var targetId = target.getCommitFromRef(targetBranch).get('id');
if (!upstream[targetId]) {
  // diverged: pull/rebase first instead of pushing

Try / catch

try { engine.push(src, dest); } catch (e) { if (e instanceof GitError && /fast/i.test(e.msg)) { /* pull then retry */ } else throw e; }

Prevention

When it happens

Trigger: Calling fetch or push where target.getCommitFromRef(targetBranch) yields a commit id not in Graph.getUpstreamSet(source, sourceBranch) — e.g. pushing to a remote branch that has diverged, or fetching into a ref that already has newer commits, without force/options to override.

Common situations: Someone else (or another level/simulation step) advanced the remote branch and you try to push over it; in LearnGitBranching, attempting a non-FF push after the origin branch moved.

Related errors


AI-assisted analysis of pcottle/learnGitBranching@5b09d0ff96 (2026-08-27). Data as JSON: /api/errors/7a234942b76f319b. Report an issue: GitHub.