TL;DR
You should probably create new branch and cherry-pick your commit into it. You can merge with --allow-unrelated-histories
. Either way you need at least one new commit, to tie into the existing branches.
Long
This part is the key to the problem:
I was on my laptop without an internet connection ...
You made, on your laptop, a repository that has no relationship with the other repositories.
git init
I assume you did this in an empty directory, so that it actually created a new, empty repository. Your new repository has no commits, and therefore no branches, even though you are (contradictorially) on branch master
. That is, you're on master
, but master
doesn't exist!
git remote add origin git://repo_address
This adds a remote but does not contact the remote (which of course would have been impossible). As a result, your repository continues to have no commits.
git checkout -b webapp <- created a new branch
In fact, this has not yet created the branch. All it has done is change you from being on the nonexistent master
branch to being on the nonexistent webapp
branch.
Then I did:
git add .
git commit -m "something"
This is where the branch actually got created, when you made, in this empty repository, its very first commit. And, since this is the first commit in the repository, it has the special property that it has no parent commit. Git calls this a root commit.
git push origin webapp
This, of course, requires Internet access, because it calls up the Git at the URL you supplied. However, as long as you have access, what this does is transfer the commits you made—the single root commit—intact, with all its parents (it has none) and all its files, and ask the other Git, at repo_address
, to change or create its branch name webapp
to match the last commit on your own webapp
.
Everything is OK so far, but now you have a repository with two roots
The Git repository at repo_address
now has a commit graph that looks like a much more complicated version of this:
A--...--M <-- master
\
N <-- development
O <-- webapp
I've assumed just a few commits (so that I can number them with single letters rather than big ugly hash IDs). Commits A
and O
are root commits. All other commits descend from A
or O
(but nothing actually descends from O
, unless you made more than one commit on your laptop).
Unfortunately when I tried to git pull
, this happened:
* [new branch] development -> origin/development
* [new branch] master -> origin/master
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
The git pull
command just runs git fetch
followed by a second Git command. In this case, it ran git fetch origin
with no additional constraints (because there's no upstream set in your repository for your webapp
), so your Git called up the other Git, the one at repo_address
, and downloaded all the commits and branches they have that you don't—which at this point, is the master
and development
work and those commits. Now you have the same graph that they have, except that your Git uses remote-tracking names rather than branch names:
A--...--M <-- origin/master
\
N <-- origin/development
O <-- webapp (HEAD), origin/webapp
Note that you still have your own branch name, and both names—your branch name webapp
, and your remote-tracking name origin/webapp
that remembers their branch name webapp
—point to commit O
, your new root commit.
The second command that git pull
runs is normally git merge
. This merge needs an upstream setting if you haven't specified what to merge, and since you did not have an upstream and did not specify what to merge, this part just failed.
Then you ran:
git pull origin development
which specified what to merge: the commit identified by their development
, that your own Git is remembering via the remote-tracking name origin/development
. (I drew this as commit N
, in my guess-at-a-graph.)
The git merge
command works by finding a shared commit. This is the "nearest" (whatever that means exactly: it's kind of intuitive from a graph drawing) commit that's on both branches. One of the two branches is your current branch, i.e., the one your HEAD
is attached to. The other branch is the one found from the commit you named, i.e., commit N
. So Git walks from commit N
back towards its root, commit A
, to find the appropriate shared commit. It also walks back from commit O
towards its root, to find the shared commit. But O
is a root, so the walk backwards from O
stops at O
. There is no shared commit!
This is why Git complains. The history attached to commit O
consists of one commit, which is commit O
itself. There's much more history behind commit N
, but it never leads to commit O
. With no shared history, merging does not make sense.
Cherry-picking
Cherry-picking consists of turning a commit—which is a snapshot—into a change-set to see "what happened" in that commit, then applying that same change-set to some other commit. A change-set is just a list of diffs: instructions for turning one commit into another commit. That's what git diff
shows, for instance. To turn a commit into a diff, Git compares the commit to its parent.
Clearly, cherry-picking a root commit is a little tricky, but Git can do it. What it does is to compare the root commit to an empty commit (well, more precisely, an empty tree). The resulting diff consists of commands reading: Add this new file; here are the contents.
So you can now check out your existing branch name development
, except for one problem: you don't have an existing branch named development
. Remember, you have this:
A--...--M <-- origin/master
\
N <-- origin/development
O <-- webapp (HEAD), origin/webapp
But git checkout
will create a branch named development
for you, if you ask it to check out development
, because it will find that there's origin/development
that looks enough like development
that it will go ahead and create the local name, pointing to the same commit, and then make it your HEAD
:
A--...--M <-- origin/master
\
N <-- development (HEAD), origin/development
O <-- webapp, origin/webapp
You can now create another new branch, webapp2
for instance, that also points to commit N
, using git checkout -b webapp2
:
A--...--M <-- origin/master
\
N <-- development, origin/development, webapp2 (HEAD)
O <-- webapp, origin/webapp
and now you can merge or cherry-pick commit O
.
Branch names vs branches
I'll outsource most of this section to another StackOverflow question: What exactly do we mean by "branch"? When we talk about "branches" here, if we don't mean branch names, we mean the series of commits ending in the branch tip commits like M
and N
. You will want your work to relate (in branch-graph-structure or branch-ancestry terms) to these existing branches. That means you want your new commit(s) to link back to commit N
, the current tip of development
.
If you cherry-pick
Suppose you create webapp2
as above, then run git cherry-pick webapp
. Git will copy commit O
to a new commit O'
that applies to the current commit N
. Git will do this by executing all those "create new file with these contents" operations that the git diff
produced by comparing commit O
to an empty tree. Since the new files don't interfere with any existing files, this should all just work.
When Git makes the new commit, it will drag the current branch webapp2
forward to accommodate it, and you will have:
A--...--M <-- origin/master
\
N <-- development, origin/development, webapp2 (HEAD)
\
O' <-- webapp2 (HEAD)
O <-- webapp, origin/webapp
which is probably what you wanted all along. Now you can delete your own webapp
, then delete webapp
in the other Git at repo_address
, and just use webapp2
everywhere, as if you had done this the way you had intended, and would have if you'd had Internet access earlier.
If you merge
You can, instead, run git merge --allow-unrelated-histories webapp
. This will tell Git to make two diffs, using that same empty tree as the common starting point. One compares to the contents of commit N
: Add every file. One compares to the contents of commit O
: Add every file. Git then combines the operations—which are all on different file names—and makes a merge commit, with two parents:
A--...--M <-- origin/master
\
N <-- development, origin/development, webapp2 (HEAD)
\
P <-- webapp2 (HEAD)
_____/
/
O <-- webapp, origin/webapp
Once again, you can delete webapp
everywhere. The weird thing is that you now have, in your history (your set of commits), this extra root commit O
.
Exercise: git merge --squash
Look up what git merge --squash
does and predict how it would leave your repository. How does this compare to cherry-picking? (Try it in a clone, if you like.)