Karl Penzhorn
Karl Penzhorn

Reputation: 395

Why does git merge create a commit in source branch

I'm trying to merge changes from a patch branch into main. This is the message I see in github:

kewp wants to merge 36 commits into patch/SEO_prices_pages from development

When I click to resolve conflicts this is the message I see

Resolving conflicts between development and patch/SEO_prices_pages and committing changes development

Why is the commit being committed into development ? I am trying to update the patch branch with the development changes...

Upvotes: 4

Views: 2297

Answers (1)

torek
torek

Reputation: 488163

There's a basic flaw in the question:

Why does git merge create a commit in source branch

It doesn't. GitHub, on the other hand ...

It's important to define terms here, and to distinguish very clearly between what Git does, and what GitHub does. GitHub is not Git! GitHub will host a Git repository for you, and then they add on their own features, to get you to use their service (instead of some competitor's). So when you use GitHub's add-ons, they may well do things that Git itself does not do. (If you like those things, GitHub are succeeding: remember, their goal is to get you to use them, and ultimately, to get you or someone else to pay them.)

The setup

[GitHub tells me] kewp wants to merge 36 commits into patch/SEO_prices_pages from development [but there are merge conflicts]

I'll assume here that kewp is you and that development is the name you gave GitHub when you sent some commits from your laptop/desktop/whatever computer, to the repository over on GitHub. Then you used GitHub's web interface to generate a GitHub Pull Request (PR), using your development as the merge-from branch and patch/SEO_price_pages as the merge-to branch, all within a single repository over on GitHub (so that no extra GitHub fork stuff is involved here).

A GitHub PR is not a Git merge. What GitHub do when you click on the "make PR" buttons does, however, involve attempting a git merge in a Git repository stored on GitHub. This attempt may either succeed or fail. If it succeeds, the resulting merge commit can be found in the Git on GitHub; if not, there is no such merge commit.

A GitHub PR is assigned a number, unique to that repository on GitHub. (It's a number that is global across all operations within that repository: issues and PRs share the number space, so in a repository with no activity yet, if you create a few dozen issues before creating any PRs, the first PR will have a two-digit number.) This number becomes a Git namespace of the form refs/pull/number. Within this namespace, GitHub create either one name, or two:

  • refs/pull/number/head refers to the tip commit of the branch you asked to merge from; and
  • refs/pull/number/merge, if it exists, refers to the merge commit that GitHub successfully made.

This merge commit is not (yet) on any branch. If it exists, it exists in the Git repository over at GitHub, and is findable under the merge name, but the phrase commit C is on branch B in Git is generally interpreted to mean that commit C is reachable from the tip commit of branch B.

(For much more on reachability, see Think Like (a) Git.)

Again, if the merge attempt fails, there is no merge commit here. Since there were merge conflicts in your case, there is a refs/pull/number/head name, but there is no refs/pull/number/merge name.

How merge works on your laptop

When you use Git on your laptop, you might perform a merge like this:

git checkout patch/SEO_prices_pages
git merge development

If there are conflicts, your Git will stop in the middle of the merge, with the results of the partially-completed merge left in both Git's index and your work-tree. You can now resolve these conflicts, using the stuff Git left behind in its index and your work-tree, any way you like. You then tell Git that you have resolved the conflicts, and use:

git merge --continue

to resume (and finish this time) the merge. The result is a new commit. The new commit is on—and at the tip of—the branch you had checked out, i.e., patch/SEO_prices_pages. The branch you named in your git merge command, i.e., development, is entirely unchanged: that name still holds the same commit hash it held before you started all of this.

The merge resolution process occurred in Git's index, but your assist almost certainly used your files in your work-tree: your computer's non-Git commands can't use the files that are in Git's index, as they're in a special, compressed, Git-only format. To use an index file, you have to copy it out somewhere (typically with git checkout or git checkout-index), then work on it there, then copy it back over the old index file (typically with git add).

The final commit has two parents: its first parent is the commit that was the tip of patch/SEO_prices_pages a moment ago, and its second parent is the commit that still is the tip of development.

How conflict resolution works on GitHub

The Git repositories on GitHub have no work-tree. (Each one does have an index, but it's mostly just vestigial.) This means that the usual tools are just not available.

What GitHub offer, at least today, is a sort of a trick. They save some of the conflict information somewhere—there's only one index and there might be multiple PRs "in flight", so it's not at all clear where, although Git does have the ability to use an alternative index, so perhaps there's an index per PR, though I would guess not—but since there is no work-tree, there is no place to just edit a file and then git add it again.

Instead, what GitHub do when you use their conflict resolution tool is make more commits. They could make these commits by advancing the refs/pull/number/head ref, and I think eventually they do advance it, but according to their own information page, they add the new commit—which uses your PR branch's tip commit's files as a starting point, but makes changes that will result in the git merge not having a conflict—to your branch.

In this case, what this means is that they:

  • extract the files from the commit at the tip of your on-GitHub-repo's development;
  • allow you to make changes to each of the files that have conflicts, such that if this were made as a new commit on your development, those files would no longer have merge conflicts with the files in the tip commit of patch/SEO_prices_pages;
  • allow you to add this commit to your on-GitHub-repo.

This is the equivalent of you, on your laptop, doing:

git checkout development

(Note: this assumes your development matches your GitHub's development!) and then:

<edit each file that had conflicts>
<git add each such file>
git commit
git push origin development

except of course that by doing this directly with your GitHub repo, you don't get the commit on your laptop (yet).

Now that your GitHub repo development has this new commit, GitHub can try the git merge again. This time the merge will succeed and they will be able to create refs/pull/number/merge.

More notes about GitHub oddities

When you actually are allowed to merge on GitHub, you will see that the green "merge" button has a dropdown arrow. Using the dropdown, you'll see that there are three options:

  • merge
  • rebase and merge
  • squash and merge

The first one does just what it says. It uses a normal everyday merge—the one GitHub already made will do just fine—and tacks that on to the target branch as usual.

The second one copies each of the to-be-merged commits to new commits that extend the target branch. GitHub do this even if the to-be-merge commits are already in the right position, graph-wise, to have been fast-forwarded. (I find this obnoxious myself: they should just fast-forward.)

The last one does the equivalent of running git checkout <target>; git merge --squash .... It probably uses the tree from the existing merge, by running git commit-tree on GitHub, but the effect is the same.

Upvotes: 7

Related Questions