1

I'm at the end of building my app that has been developed on my dev branch and would like to now add it to master for production.

However, when I checkout to master and run git merge dev I receive a fatal error of refusing to merge unrelated histories. Unfortunately, git pull origin master --allow-unrelated-histories didn't help resolve the error.

This is the status of master:

  • git status shows that my "branch is up to date with 'origin/master'".
  • The only file in the repo is an empty README.md.
  • GitLab has labelled this a "stale" branch.
  • It is a protected branch.

Help me understand why am I not able to merge.

EDIT: I should also mention that git log produces the following:

commit ac22443836aeaf8abe38aa3761970a4cd3835587 (HEAD -> master, origin/master)
Author: // my info
Date:   Fri Dec 21 03:25:26 2018 -0700

    Initial commit
(END)
Joe McMahon
  • 3,266
  • 21
  • 33
Mix Master Mike
  • 1,079
  • 17
  • 26
  • Does `master` and `dev` have a common ancestor (I mean a common commit parent) ? – Jona Mar 25 '19 at 22:50
  • I went through and it actually didn't seem to share a common ancestor and that may be the crux of the issue. But it looks like a solution that was posted (but then deleted by another user...not sure why) did the trick. Basically, `git merge dev --allow-unrelated-histories`. – Mix Master Mike Mar 25 '19 at 23:16
  • How did you set up your local dev environment? Did you clone the repo you want to merge with, and then `git checkout -b dev` to create your dev branch? – Joe McMahon Mar 25 '19 at 23:20
  • @JoeMcMahon that's what I thought I did as that's my modus operandi. But for whatever reason as I went back through my logs, I didn't see `dev` branch off of `master` or share the same commit ID as `master`. So this makes me wonder. Even though this was done 3 months ago, it seems like ages. And with the fix I described above `master` is `ahead of 'origin/master' by 720 commits.` – Mix Master Mike Mar 25 '19 at 23:26

4 Answers4

1

You already fixed (?) this using --allow-unrelated-histories and there's no real reason not to just leave that. But in case you are still wondering...

What happened, and how you (probably) got here

The first key to using Git is to understand that Git is all about commits. Of course, this also requires that you understand, in a reasonably deep sort of way, what a commit is. What it is, is pretty short and simple: it's a permanent (mostly) and immutable (entirely) snapshot plus some metadata. The snapshot contains all of your files—well, all of them as of the time you made the commit—and the metadata has:

  • your name and email address, and the time you made the commit;
  • your log message, i.e., why you made this commit; and, crucially,
  • the hash ID of the commit that comes just before this commit, defined as the parent of this commit.

Every commit is unique—for many reasons, including the time-stamp mentioned above—and every unique commit gets a unique hash ID. That hash ID, some big ugly string of hexadecimal characters, seems random, but is actually a cryptographic checksum of the contents of the commit. It's also the True Name, as it were, of that commit: that ID means that commit, and only that commit. No other commit will ever have that hash ID. That hash ID will always mean that commit.

Git actually finds the commit by hash ID. So the hash ID is crucial. Of course, it's also impossible for humans to remember. So Git gives us a way to remember the latest hash ID, and that way is a branch name like master or dev.

The name only has to remember the last commit because each commit remembers its parent's hash on its own. That is, given a tiny repository with just three commits, where we replace the actual hash IDs with a single uppercase letter, we can draw this:

A <-B <-C   <-- master

The name master remembers the hash ID of C. C itself—the actual commit, retrieved by hash ID—remembers the hash ID of B, and B itself remembers the hash ID of A.

When something remembers the hash ID of some other commit, we say that this something points to the commit. So the name master points to C, C points to B, and B points to A. Those three commits—C, then B, then Aare the history in the repository.

Note that A doesn't point anywhere. It literally can't, because it was the first commit. There was no earlier commit. So it just doesn't, and Git calls this a root commit. All non-empty repositories have to have at least one root commit. Most, probably, have exactly one ... but yours has two.

Normal branching and merging

Let's take a quick look at the more normal way to make branches. Suppose we have just these three commits, pointed-to by master. We ask Git to make a new branch name, pointing to the same commit as master:

A--B--C   <-- dev (HEAD), master

Both names identify commit C, so commit C is on—and is the tip commit of—both branches, and all three commits are on both branches. But now we make a new commit. The process of making a new commit—with the usual edit and git add and git commit—makes a new snapshot, adds our name and email and timestamp and so on, uses the current commit C as the saved hash, and builds the new commit. The new commit gets some big ugly hash ID, but we'll just call it D:

A--B--C   <-- dev (HEAD), master
       \
        D

Since D's parent is C, D points back to C. But now the magic happens: Git writes D's hash ID into the current branch name—the one HEAD is attached to—so now we have:

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

and voila, we have a new branch. (Well, we had it before, pointing to C. Most people don't like to think about it that way though, they want to call D the branch. In fact, the branch is D-C-B-A!)

Over time we add some commits to both branches:

A--B--C-----J----K----L   <-- master
       \
        D--E--F--G--H--I   <-- dev

We git checkout master and git merge dev. Git will find the merge base commit for us, where dev and master diverged. That's obviously commit C since that's where the two branches rejoin in the past. Git will compare C vs L to see what we changed on master, compare C vs I to see what we changed on dev, and combine the changes. Git applies the combined changes to the snapshot in C—to the merge base—and makes a new merge commit M, which goes on our current HEAD branch as usual, updating that branch name so that master points to M:

A--B--C-----J----K----L--M   <-- master (HEAD)
       \                /
        D--E--F--G--H--I   <-- dev

What's special about M is that it has two backwards links: it goes back to L, as all commits would, but it has a second parent I, which is the current tip commit of branch dev. Other than the two parents, though, it's quite ordinary: it has a snapshot as usual, and our name and email and timestamp and a log message.

Abnormal branching

There's nothing in Git that stops you from making extra root commits. It's just a little bit tricky. Suppose that, somehow, you did this:

A   <-- master

B--C--D--...--L   <-- dev (HEAD)

Once you have this situation, git checkout master; git merge dev just gives you an error. That's because the usual method of finding a merge base—starting at the two branch tips and working backwards—never finds a common commit.

Adding --allow-unrelated-histories tells Git to pretend that there's a special empty commit before both branches:

 A   <-- master (HEAD)
0
 B--C--D--...--L   <-- dev

Now Git can diff 0 vs A to see what you changed on master, and 0 vs L to see what they changed on dev. On master, you added every file. On dev, you also added every file. As long as those are different files, the way to combine those two changes is to add the master files from commit A to the dev files from commit L, apply those changes to the empty null commit, and commit the result, with parents going back to both A and L:

A---------------M   <-- master (HEAD)
               /
B--C--D--...--L   <-- dev

How you (probably) got here

There's a git checkout option, git checkout --orphan, that sets this state up. But that's probably not what you did. The state this sets up is the same state you're in when you create a new, empty repository with git init:

[no commits]   <-- [no branches]

There are no branches, and yet Git will say that you're on branch master. You can't be on master: it doesn't exist. But you are, even though it doesn't exist. The way Git manages this is that it puts the name master into HEAD (.git/HEAD, actually) without first creating a branch named master. It can't create the branch because a branch name has to contain a valid hash ID, and there aren't any.

So, when you run git commit, Git detects this anomalous state: that HEAD says master but master doesn't exist. That's what triggers Git to make our root commit A. Then Git writes A's hash ID into the branch, which creates the branch, and now we have:

A   <-- master (HEAD)

which is just what we wanted.

But suppose, while we're in this weird no-commits-yet state, we run:

git checkout -b dev

This tells Git: Put the name dev into HEAD. It does that without complaint, even though there's no master either. Then we make our first commit, but for no obvious reason, we'll pick B as its one-letter stand-in for its hash ID:

B   <-- dev (HEAD)

Meanwhile, having run git init here, then git checkout -b dev, then done something and git commit, we'll go over to $WebHostingProvider—whether that's GitHub or GitLab or Bitbucket or whatever—and use its make me a new repository clicky buttons. Those usually have an option: create an initial commit with README and/or LICENSE files and such. If that option is checked—or the don't option is unchecked—they go ahead and make a first commit and a master:

A   <-- master (HEAD)

Now you connect your repository to theirs and have your Git download any commits they have that you don't:

A   <-- origin/master

B   <-- dev (HEAD)

You can now proceed to add lots of commits, never noticing that your dev branch is not related to their master branch (which your Git is calling origin/master).

Later, you run:

git checkout master

Your Git notices that you don't have a master, but that you do have an origin/master. So your Git creates a master for you, pointing to the same commit as origin/master, and attaches your HEAD to your new master:

A   <-- master (HEAD), origin/master

B--C--D--...--L   <-- dev

and voila, you're in the pickle you were in.

torek
  • 448,244
  • 59
  • 642
  • 775
0

If --allow-unrelated-hhistories was necessary, that means you actually had the equivalent of two different repositories that you merged together.

I suspect this happened because you didn't clone the original repo first before creating your dev branch and doing work. My deduction from your description is that you did a git init, checked out a dev branch, and then added the remote repo as origin when you wanted to merge (this worked because your local repo had no remotes).

Attempting to merge at that point would get the 'unrelated histories' because the two repos are, in fact not related; the original was created and pushed to GitLab, and a completely separate one was created when you did your git init to create the local. Cloning the original would create a local repository that was related, because it shared that initial README commit.

This is, indeed, the only way to fix this issue, and as soon as you push to GitLab, the local and the remote will be in sync -- but you should always clone to avoid the problem in the first place.

Joe McMahon
  • 3,266
  • 21
  • 33
  • Were you the one that suggested running `git merge dev --allow-unrelated-histories`? Someone did then deleted this solution. But it worked. I want to give credit where credit is due. – Mix Master Mike Mar 25 '19 at 23:29
  • Wasn't me, sorry -- but that was the right answer for this situation. I've used it when creating a mono-repo out of multiple repos, so it's not inherently a _bad_ thing. It's just that, as you say, clone-branch-commit-merge/rebase is better. – Joe McMahon Mar 25 '19 at 23:31
0

If, like it was said, master and dev doesn't have a common (parent) commit, I would try to create new branch from master and cherry-pick all commits from dev into it: git cherry-pick startHash^..endHash For example git cherry-pick 1234^..5678. Then you should be able to merge this new branch into master

zdolny
  • 999
  • 1
  • 11
  • 21
0

You probably checked "[ ] Create repository with a README" when you created the repository.

When master is really empty, I see the following options

  • go to gitlab, change the default branch to something else and remove the master branch in the web interface (there, it is possible to delete protected branches). Or,

  • unprotect master

Now you can force-push your dev Branch to gitlab (e.g. git push -f dev:master).

NOTE: this option is a no-go for branches resp. repositories which are used by other peoples. In this case do:

git fetch gitlab master
git merge -s ours FETCH_HEAD

and push this branch.

ensc
  • 6,704
  • 14
  • 22
  • For clarification is `git fetch gitlab master` and `git merge -s ours FETCH_HEAD` is executed from my `master` branch, correct? – Mix Master Mike Mar 25 '19 at 23:50
  • `git fetch` can be executed everywhere (even in bare repositories). For `git merge` you have to checkout the branch which shall become `master`. – ensc Mar 25 '19 at 23:54