GIT how to merge master into branch cloned with -single-branch

Viewed 2515

I have a git repository, where master is the current version of the application and each customer has their own branch with their own customizations.

I have cloned each customers branch separately into a directory using --single-branch

My goal is to merge master into my branch. I have cloned the repo using following command:

git clone {{REPO_URL}} --single-branch my-branch --branch my-branch

Then I tried:

git fetch origin -- master

git merge master

Then I am getting this

merge: master - not something we can merge

I know I could get around this by cloning entire repository, but is there anyway to work around this?

3 Answers

You just need to add the url as another remote in your local repo.

Detail commands as below:

# In local my-branch
git remote add upstream <repo URL> -f
git merge upstream/master

Now your local my-branch contains the changes from master branch.


Update: to add remote only for master branch, you can use the command:

git remote add upstream <repo URL> -t master -f

If you already add the remote upstream with all branches, you can change it by:

git remote rm upstream
git remote add upstream <repo URL> -t master -f

TL;DR

You must:

  • merge with FETCH_HEAD, or
  • specify a destination reference name in your git fetch refspec, or
  • adjust the remote.origin.fetch settings

or some combination of these items. Depending on the combination used, you might want to fetch into refs/remotes/origin/master and run git merge origin/master, for instance.

Explanation

The key lies in refspecs. A refspec, in its second-simplest form, is a pair of references separated by a colon, such as refs/heads/master:refs/heads/master. The references can be abbreviated in some cases, e.g., master:master. The name on the left is the source, and the name on the right is the destination (which the Git documentation abbreviates as <src> and <dst>).

Consider, then, your command:

git fetch origin -- master

(The -- is pointless here.) The git fetch command treats origin as the name of the repository (so it looks up the URL associated with origin, and the fetch = refspecs associated with origin), and then treats master as a refspec, to combine with and partly override any previously-defined fetch refspecs.

When you make a --single-branch clone:

git clone {{REPO_URL}} --single-branch --branch my-branch

this changes the fetch refspecs associated with the new clone. (Note: the additional my-branch argument winds up being treated as a directory name.) Running:

git config --list --local

will show you, among other items:

remote.origin.url=... whatever the URL expanded to...
remote.origin.fetch=+refs/heads/my-branch:refs/remotes/origin/my-branch

This tells your Git that when it contacts the other Git at origin, the default action is to fetch only their branch my-branch, copying the result to your remote-tracking name origin/my-branch.

When you run git fetch origin with no additional arguments, your Git performs this default action.

Without --single-branch, Git would have used the default refspec, fetch=+refs/heads/*:refs/remotes/origin/*. This would direct Git to obtain all branches (all refs/heads/ names), and copy them to remote-tracking names in your own repository.

When you give git fetch origin additional arguments, they are refspecs, just like this fetch setting. You could, for instance, write:

git fetch origin +refs/heads/normalbranch:refs/strangeref

which would direct your Git to copy their (local to them, and quite normal) branch normalbranch to a local reference in your own repository named refs/strangeref.

Now, I said above that a pair of names is the second simplest form of refspec. You are using the simplest: a single name, master. There is no colon; there is no second name. This is still a valid refspec, but what it means is different in git fetch and git push. Since we're only concerned with git fetch, let's see what it means there, which is:

<refspec>

    Specifies which refs to fetch and which local refs to update. ...
    The format of a <refspec> parameter is an optional plus +, followed by the source <src>, followed by a colon `:`, followed by the destination ref <dst>. The colon can be omitted when <ds> is empty. ...
    The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local ref that matches it is fast-forwarded using <src>. ...

Since you don't have the : and destination, the <dst> part is empty, and no local reference is fast-forwarded. (A leading plus sign + would tell Git to force-update the local reference even if fast-forwarding would fail, but you omit the local reference in the first place, so there's nothing for the + force flag to affect.)

What this means is that the commits git fetch brings over are recorded only in the old, backwards-compatible with Git version 1.5, FETCH_HEAD file. So you'd have to git merge FETCH_HEAD to merge the commit whose ID is saved in FETCH_HEAD—the tip commit of the other Git's master branch.

Other alternatives

If you supply, on the command line, a destination refspec that does not exist:

git fetch origin master:refs/heads/tmpbranch

(which in this case creates a new local branch named tmpbranch), you could then supply that as the argument to git merge:

git merge tmpbranch

and then delete it:

git branch -D tmpbranch

so that you are ready for the next time (there's no tmpbranch lying around to make the git fetch potentially fail).

Or, you could use the force-flag to forcibly create-or-update your own master branch:

git fetch origin +master:master

(this relies on Git's automatic qualification turning master into refs/heads/master—if you're scripting, it's probably better to spell it out explictly; automatic qualification is for lazy humans, not for computers). Then you can git merge master.

Note that this will overwrite your own local master, if you have created one and are using it. For this reason, you might want instead to use a remote-tracking name:

git fetch origin +master:refs/remotes/origin/master

after which you would run git merge origin/master.

Finally, you could direct your Git to always-by-default fetch the other Git's master:

git config --add remotes.origin.fetch '+refs/heads/master:refs/remotes/origin/master'

Now you can just run:

git fetch origin
git merge origin/master

Fetching from origin will now, because there are two remote.origin.fetch settings, fetch both of the other Git's branches, mybranch and master, placing the fetched hash IDs into your remote-tracking names, refs/remotes/origin/mybranch and refs/remotes/origin/master.

(Compare this with the default refspec, +refs/heads/*:refs/remotes/origin/*, which tells your Git to fetch all branches and create or update a remote-tracking name whose name matches the branch name in the other Git.)

I had cloned a single branch, that too with depth 1 (single commit):

git clone --depth 1 --single-branch branch_to_merge

now I wanted to merge this into a new branch "destn_branch" but hadn't cloned it in my local so I had to first fetch the branch again...

git fetch --depth 1 origin destn_branch:destn_branch

(I should have not done depth 1, but I was left with no choice :(, the commits were too many >_>)

now I could do

git checkout destn_branch
git merge branch_to_merge

but git said "are u crazy, those 2 branches are unrelated" (that is "uncommon history" something, I forgots the text)

so I one-upped git by re-running the above commands with a deeper depth so that they shared a common historical commit and git could understand that it can link the 2 branches from there on... so, I re-ran the following commands and it worked:

// fetch 3 latest commits of branch_to_merge
git checkout branch_to_merge
git fetch --depth 3

// fetch 4 latest commits of destn_branch - this way one of the fetched commits is the same as in other branch's fetched commit
git fetch --depth 4 origin destn_branch:destn_branch

// now do a normal merge
git checkout destn_branch
git merge branch_to_merge

and they called me stingy for always doing depth-1 single-clone pulls -_-, now who is prudential >_>

Related