The source branch is 1 commit behind the target branch

Your image shows what's going on. Unfortunately the image is hard to read (tiny, and, well, an image). But it does show the problem. Explaining the problem in words, and drawing it in text, requires a bit of background.

A Git repository consists primarily of commits. Each commit in some repository:

  • Has a unique ID (a "hash ID", which command-line Git will show you as either abbreviated, such as a123456, or in full). These things look random—though they're not at all random—and are not very useful for humans, so a lot of graphical commit viewers, including yours, omit them. Sometimes that's good, and sometimes that's a terrible disservice to you; in this case it's more a little bit of both.

  • Stores two things:

    • Every commit stores a full snapshot of every file. These act like permanent archives. The files in this snapshot are stored in a special, compressed, and de-duplicated, Git-only format. When you make a new commit, you'll generally re-use almost every file from the previous commit. Through the de-duplication trick, Git won't actually store those files again. Nonetheless, each commit "has" all the files: they're just shared, automatically, without you having to think or worry about it.

      This sharing has a strong consequence: no part of any existing commit can ever be changed, not even by Git itself. (Git's hashing technique depends on this as well.) Commits are therefore entirely read-only, once made. They're also mostly permanent, though you can "eject a commit off the end of a branch" in some cases.

    • Every commit stores some metadata. This includes things like the name and email address of the person who made the commit, and some date-and-time stamps. It also includes a log message where you describe why you made the commit: yours read Initial commit, pushing a file, pushing xyz file to branch, and pushing pqr file to branch.

      Git includes, in this metadata, the raw hash ID of the previous commit. In this way, every commit remembers which commit you used in order to create it.

As we already noted, each commit is read-only. Git finds the commit by its hash ID, not—well, not yet—by a branch name. Commits may be on multiple branches, or even on no branch at all, so there's no way to go from commit to branch name. So: what is a branch name, and what does one do for you?

You have two branch names in your repository: master and some_changes. Your graphical viewer shows you these as a sort of label, attached to a colored dot that represents some commit. Horizontally to the right of the colored dot, your viewer shows you the log message (or at least its first line). This arranges the commits in time, sort of, with newer commits higher up towards the top of the window:

master ----------> * pushing pqr file to branch
some_changes --> * | pushing xyz file to branch
                  \|
                   * pushing a file
                   * initial commit

This is an attempt to show you all commits at once, without having "time" get in the way very much. But it doesn't work. Time gets right there in the way: your latest commit is at the top and your earliest commit is at the bottom.

I like to draw these commits differently. I tend to use a single uppercase letter to stand in for a commit hash: H stands for "hash", for instance. Commit H holds inside itself—in its metadata—the raw, random-looking hash ID of some earlier commit G. Both commits hold full snapshots of every file, as of the form those files had when you made those commits. Commit G holds in its metadata the raw hash ID of still-earlier commit F, and so on.

When these are all in a row, we get a linear chain, which I draw left-to-right, with newer commits towards the right, like this:

... <-F <-G <-H   <--branch-name

The branch name holds the raw hash ID of the latest commit. It's a label—just like your viewer drew—and it allows Git to find commit H, because the name holds the hash ID of H.

Sometimes, though, our commits aren't all neatly linear:

     C   <-- master
    /
A--B
    \
     D   <-- some_changes

Here we have two labels, master and some_changes. The name master points to commit C, which is the latest. The name some_changes points to commit D, which is also the latest. There are two "latest" commits. To draw this "correctly" (for some definition of correct), we can't just put them all in a row, either horizontally or vertically. Hence in my text diagram I put the branch names and arrows at the far right, and have them point leftward to the latest commits. Those commits then point further left—in this case, down-and-left from C to B, and up-and-left from D to B—so as to find, for Git, the parent commit.

In this way, commits A and B are on both branches. That's because we can find them by starting at C and working backwards and by starting at D and working backwards.

The job of git merge is to combine work, and Git will often be able to do that all on its own. In GitLab (but not Git—Git does not have "merge requests"), a merge request is a way to identify two particular commits—in this case, C or pushing pqr file to branch, and D or pushing xyz file to branch—and ask that some human make Git merge those two commits.

Note that commit C is not before commit D, nor is it after commit D. The two commits are both one commit after B, but the two commits are actually parallel. After a successful merge, however, we'll have this:

     C
    / \
A--B   M
    \ /
     D

where M is a new merge commit that combines the work done in the two branches. I've deliberately removed all the branch name labels here: Git only uses them to (help you) find commits; it's the actual commits that really matter. Of course, since humans do use names to find commits, the name that ultimately ends up pointing to commit M will matter to you. It just won't matter to Git.

Linear "merges"

Some people like it when proposed merges are all linear:

W--X--Y   <-- master
       \
        Z   <-- branch

Here commit Z is one commit ahead of commit Y. As you can see, there's no other "branch-y" structure sticking out: unlike our A-B case where C and D are parallel, here Y is definitely before Z.

Git can merge this exactly as before:

W--X--Y---M   <-- master
       \ /
        Z   <-- branch

When Git goes to combine work, though, there's no work on master that isn't also already on branch. So by default, Git will not do this, but instead will do this:

W--X--Y--Z   <-- branch, master

That is, you'll end up with both labels pointing directly to commit Z, without making any new commit M. Once we erase the branch label, we're left with:

W--X--Y--Z   <-- master

which leaves us with a simplifying illusion: it looks like we never bothered with any branch.

Again, some people like this, some hate it, and some are neutral. Git offers either method. Some web hosting sites, however, do not offer this. (I have not used GitLab and am not sure what GitLab's defaults are here, and whether it lets you do what Git lets you do.)