A partner and I are working on a project and I cloned from her branch to use what she had as my starting off point. Well the project is tied to her branch and I am trying to find a way to untie it. I am new to github and would like to know of any way that I can push the changes I made to my own branch in the same repository. Thanks for any help.
-
you can research more on [git remote](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) – AlexWei Dec 06 '20 at 16:06
1 Answers
... and I cloned from her branch
Actually, you didn't. That's simply not possible. When you use git clone
, you clone a repository, and the only from involved is a URL. You clone from a URL, and in general, you clone the entire repository.1 This means your next job is easy, or at least, easier.
1The exceptions to these rules involve using --depth
for a shallow clone, using --single-branch
—note that --depth
implies this flag too—and/or using the not yet ready for general-use --filter
option to produce a partial clone. If you did not use any of these special options, you got a full clone.
... the project is not tied to her branch and I am trying to find a way to untie it.
In Git, branches are not important. They're useful—branch names are how you find commits—but they are not important, in that you can change them any time, add new names, delete old names, and so on. What matters are commits. We'll get back to this in a moment.
I am new to github
Except when you are using the web interface to GitHub, the presence of GitHub in this is all irrelevant. You will do your work on your own computer, using your everyday computer tools, and the tools provided by the Git system (which add on to your everyday computer tools).
and would like to know of any way that I can push the changes I made to my own branch in the same repository.
This part is trivial, but you might want to learn something less trivial: you don't really have changes, for instance. You have commits. Those commits store files—particular versions of files, stored for all time2 in the form they had at the time you made those commits.
2Well, stored for as long as the commits themselves continue to exist anywhere.
Git is all about commits
Those new to Git, like yourself, often think Git is about branches (as you did). But it's not: it's about commits. Branches—or rather, branch names—just help you (and Git) find commits. Others often think Git is about files. It's not about those either. Commits contain files, because files are important to humans, to get their work done. But Git is about the commits.
Every commit in Git has a number. This number is big, ugly, and random-looking—though not actually random at all—and expressed as, e.g., faefdd61ec7c7f6f3c8c9907891465ac9a2a1475
. The number is unique to that one specific commit and once assigned to a commit, can never be used for any other commit. (That's why it has to be such a big number.) This way, any two Git programs, looking at any two Git repositories, can talk to each other and figure out whether one repository has the same commit as some other repository, just by exchanging the numbers.
With that in mind, we can now say that a repository is, in essence, just two databases. The primary, and usually much bigger, database is a simple key-value store that holds internal Git objects, such as commits.3 The key is the number and the value is the internal Git object: the commit itself, in the case of an internal commit object.
(All Git objects—hence all commits—are read-only. The reason is that the hash ID is simply a cryptographic checksum of the contents of the object. If you copy one of these objects out of the database (e.g., extract a commit) and modify it and put it back in, what happens is that you add a new object with a new unique hash ID. The existing objects continue to exist. The only way not to add a new object is to exactly duplicate an existing one.)
What's in a commit is a little more complicated, but we can summarize it this way. There are two parts to each commit:
A commit holds data, which is a snapshot of all the files that Git knew about at the time you, or whoever, made the commit. It's a complete snapshot, not a set of changes. The files' contents are stored internally as blob objects, which automatically de-duplicates them, so it's cheap to store all the files all over again: Git just re-uses the old object.
A commit also holds metadata, which is information about the commit itself. This includes the committer's name and email address, and a date-and-time-stamp for when they made that commit. It includes any log message they want to add to the commit. And, crucially for Git itself, it includes the hash ID(s) of some set of earlier commits.
This last part—a commit storing the hash ID of what Git calls its parent commit—is how history works. Suppose we have a simple linear chain of commits, each with a unique hash ID. Let's draw their hash IDs as single uppercase letters, because real hash IDs are too big and ugly to deal with. We end up with this:
... <-F <-G <-H
where H
stands in for the hash ID of the last commit in the chain.
Using the hash ID of commit H
, Git can get at a full snapshot of all the files as of the form they had when you made commit H
. But it can also get the metadata, which includes the hash ID of earlier commit G
. We say that commit H
points to earlier commit G
.
Moving backwards one step, then, Git can now use the commit hash ID of G
to get G
: the snapshot of all files, and the metadata. Git can compare the snapshot in G
against the snapshot in H
to see what changed, if you like. But Git can also use the metadata from G
to find the hash ID of still-earlier commit F
.
Since each commit points backwards one step, all we need, to find all the commits in the chain—all the way back in history to the very first commit ever—is the hash ID of the last commit in the chain.
3Internally, there are four kinds of Git objects; all get these hash ID numbers, and all are stored in this database.
Branch names find commit hash IDs
To make use of the chain above, we need to know the hash ID of the last commit in the chain, the one we called H
. We could write this hash ID down, or memorize it. But that would be silly: we have a computer. Let's just have the computer remember it, perhaps in a file. We could name the file feature
or branch1
for instance.
This is the function of a branch name. Each branch name simply holds the hash ID of one commit: the last one in some chain. Suppose we have this chain:
...--F--G
\
H
where H
is the last one, and G
is also a last one because H
is in a different branch. We just need names to point to commits G
and H
, like so:
...--F--G <-- main
\
H <-- develop
Here, develop
finds a new commit, added since the last stable commit, which we find with the name main
.
What Git really needs are the commits. We need to know that commit G
is the last one on main
, so we need the name main
to point to G
. We need to know that H
is the last commit on develop
, so we need the name develop
to point to H
. But Git really only needs the commits.
Picking a branch name to use, and getting the files out of a commit
The files in a commit are all read-only. They are also in a form that only Git itself can read (internal objects are not usable by most non-Git programs). They literally cannot be used for new development. If we want to add a new commit to develop
, we have to get Git to copy the files out of commit H
and turn them into regular everyday files. We also need to have Git remember that the commit we did this with was commit H
.
The way we do this is with git checkout
or (since Git 2.23) git switch
. We pick some branch name to use and have Git get "on" that branch. The way Git remembers the branch name is that it attaches the special name HEAD
to the branch name:
...--F--G <-- main
\
H <-- develop (HEAD)
It also copies all the files out of the commit at this time. The copied-out files go into your working tree or work-tree, where you can see them and work on/with them. These files are not actually in the Git repository at all. They were copied out of the repository and you can get work done now, but if you do change the files, you will, later, need to have Git make a new snapshot.
You already know how to do this, using git add
and git commit
. You may or may not know about Git's index, which Git also calls its staging area; we won't go into any details here except to mention that git commit
actually uses the copies from the index / staging-area, not the copies from your working tree.
When you do urn git commit
, Git packages up the files it knows about (because they're in its index / staging-area). Git adds the appropriate metadata, including your name and email address and the current date-and-time as set in your computer's clock. Git adds the hash ID of the current commit, and uses all of this stuff to create a new commit:
...--F--G <-- main
\
H--I
As soon as Git finishes writing out commit I
—which assigns the commit its new unique hash ID4—Git writes that hash ID into the name to which HEAD
is attached, giving us this:
...--F--G <-- main
\
H--I <-- develop (HEAD)
Your HEAD
is still attached to your branch name develop
, and your branch name develop
now selects commit I
.
4Note that the hash ID depends on the exact second at which you make the commit. The date-and-time-stamp is part of the metadata, and the cryptographic checksum includes that date-and-time-stamp. This means that there's no way to predict, in advance, what hash ID some commit will get. You just have to make the commit; that's what gives the commit its hash ID.
Cloning a repository
Before we go on to the next step, let's take a look at how git clone
really works. This also involves the git fetch
command, so it's a pretty big step.
The clone command is really a convenience command. It wraps up a series of six commands all into one. Five of these six commands are Git commands. The first one is just your own computer's "make a new directory / folder" command:
mkdir
(or whatever your computer's command is): this makes a new, empty directory. When you rungit clone url
, Git takes the last part of theurl
and turns that into the directory name. For instance,git clone https://github.com/git/git
clones a Git repository for Git itself. The last part of this URL isgit
so your own Git will make a new emptygit
directory.
(The remaining five commands are all run in the new directory, though when this is all done, you're still not in the new directory. You will have to move your command interpreter into that directory with, e.g., cd git
, or whatever your command line interpreter uses.)
git init
: this creates a new empty repository in the otherwise empty directory. The repository goes in a sub-directory named.git
.git remote add origin url
: this adds a remote namedorigin
to this new empty repository. A remote is mainly a short name for a URL, so that Git can expandorigin
to this URL again later. However, the nameorigin
is also used in step 5. (You can pick a different name, other thanorigin
, here, if you like, but there's usually no reason to choose something else.)The clone command now runs any
git config
commands required by command line options you gave togit clone
. If you didn't use any extra options, step 3 here doesn't actually do anything; I include it only for completeness, to show that the configurations happen before step 5.git fetch origin
: this is the most complicated part of the operation. This step fetches all the commits from the other Git repository, but none of the branches.5 The fetch command works by examining their branch names and corresponding commit hash IDs, and using this information to get any commits they have, that you don't. This is (normally—see footnote 5) all of their commits. So this step gets you all their commits. Butgit fetch
then takes their branch names and changes them. It makes them into what Git calls remote-tracking names, which aren't branch names.6 Theirmain
becomes yourorigin/main
; theirfeature/tall
becomes yourorigin/feature/tall
; theirdevelop
becomes yourorigin/develop
, and so on.git checkout branch
: this step creates one new branch in your repository. Note that your branches are yours, not anyone else's. Their branches became your remote-tracking names.
The new branch your Git creates here is your choice: it's from the -b
option you gave to git clone
. If you did not give a -b
option to git clone
, your Git asks their Git what branch name they recommend, and uses that name here. This still creates your own branch name.
The end result of all of this is that before the last step, your own repository has, e.g.:
...--F--G <-- origin/main
\
H--I <-- origin/develop
The special name HEAD
can't be attached to a remote-tracking name (which is half of why they're not branch names). The last step, to run git checkout
, creates a new branch name. It's spelled the same as one of these origin/*
names, minus the origin/
part, which means it's spelled the same as one of the other Git's branch names; and it points to the same commit as one of those names. Let's say you picked develop
as the name for your Git to create. Then you now have:
...--F--G <-- origin/main
\
H--I <-- develop (HEAD), origin/develop
in your own repository, with commit I
checked out.
5There are some special cases where this isn't quite right, including of course the --depth
and --single-branch
ones I mentioned at the top. The fetch operation can be limited. It can also be made to copy branch names, rather than renaming branches. But these are unusual modes of operation, and we won't go into them here.
6Git actually calls them remote-tracking branch names. But they're still not branch names, and the word branch in this is just a distraction. I recommend leaving it out.
With all this in mind, we can get back to your question
[my] project is not tied to her branch and I am trying to find a way to untie it.
You should now look at your repository and find which commits you have, and which branch and remote-tracking names find those commits. Consider using git log --all --decorate --oneline --graph
or any other Pretty git branch graphs viewer.
Now, suppose you have made new commits that follow someone else's commits:
...--G <-- origin/main
\
H--I <-- origin/develop
\
J--K <-- develop (HEAD)
To make your changes into new commits, first you're going to have to extract changes. Commits hold snapshots, not changes. But Git can easily find changes, because any two adjacent snapshots can be compared, like a simple game of spot the difference. Git has a difference-spotter built in.
We need a new name so that Git will remember new hash IDs for us
Your existing commits J
and K
, in this illustration, literally can't be changed. That's not a problem, just something to keep in mind.
Let's first create a new branch name and make it point to existing commit G
, just like your origin/main
points to commit G
:
git branch newname <hash-of-commit-G>
or:
git branch --no-track newname origin/main
These two commands result in this:
...--G <-- newname, origin/main
\
H--I <-- origin/develop
\
J--K <-- develop (HEAD)
We then need to run git checkout newname
to move HEAD
to the name newname
, and get commit G
checked out:
...--G <-- newname (HEAD), origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
There's a reason we added --no-track
to the command that used origin/main
. It's not really very important and we don't actually need to do that at all, and this flag is a bit confusing, so I'm not going to bother explaining it either. There's a different way to create the name newname
, using two Git commands:
git checkout main
git checkout -b newname
This has a slightly different effect and is itself shorthand for:
git branch main origin/main
git checkout main
git branch newname
git checkout newname
The first step creates a new branch name main
in your own repository, using the name origin/main
as the commit-finder, so that main
points to commit G
. Since we didn't add --no-track
, the new name main
has origin/main
set as its upstream.7 This is true whether we use git checkout -b
or git branch
to make the name. If we use git branch
to make the name main
, we then use git checkout
to get on the new branch, so that Git extracts commit G
for us to work with. Either way we end up with the special name HEAD
attached to the new name main
:
...--G <-- main (HEAD), origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
The second step, git checkout -b newname
or the two commands git branch newname
and git checkout newname
, makes a new branch name, newname
, pointing to the current commit (G
, still) and then attaches HEAD
to that name:
...--G <-- main, newname (HEAD), origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
The difference here is that we created a new name main
to identify commit G
, before creating the new branch name newname
to also identify commit G
. We don't actually need the branch name main
at all: the remote-tracking name origin/main
works just as well for our purposes. It's up to you whether to bother to create the name main
.
Note: it's perfectly fine to create the branch name main
now, and then, tomorrow, if you decide you didn't want it, to simply delete the branch name main
. You can add and delete branch names any time you like! The only thing to remember is that the name is an easy way to find any one particular commit, so if you want to find that one commit easily, you might want to keep that name. The point of a branch name is to let you, and Git, find a commit—and to let you get "on" that branch and make new commits that automatically update the branch name. If you don't need an automatically-updated name, you don't need a branch name, but you can still use one any time you feel like it.
7The word upstream was developed later, after the --track
and --no-track
options. These use the word "track", which is badly overloaded in Git. The word "upstream" is a better word here, but for now we're stuck with --track
and --no-track
as the option spellings. You don't need to remember all of this.
Now that we have our new branch name, let's copy the commits
To make our new branch independent, yet still have some new commits on it, let's copy the existing J-K
commits. Remember, we're assuming at this point that we have this picture:
...--G <-- newname (HEAD), origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
That means there are two commits that are "on" our own develop
that we'd like to copy. We don't want to copy commits H
and I
, only J
and K
need copying.
The command to copy a commit—that is, turn a commit into differences from its parent commit, and then apply those differences to our current commit and make a new commit from that—is git cherry-pick
. We can do this one commit at a time:
git commit <hash-of-commit-J>
for instance. To get the hash of commit J
, we can use git log
or one of those fancier pretty-commit-graph viewers to find the hash ID for commit J
, then just cut-and-paste it. We'll see an easier way in a moment, but let's just imagine we did that.8 We get this as the result:
J' <-- newname (HEAD)
/
...--G <-- origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
The cherry-pick operation has copied commit J
, to a new-and-improved version J'
. The change from G
to J'
, in the copy, will be the same as the change from I
to J', in the original. The metadata will mostly be copied, except for some updated timestamps and that the parent of new commit
J'is existing commit
G, not commit
I`.
We then repeat this for commit K
, producing:
J'-K' <-- newname (HEAD)
/
...--G <-- origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
We now no longer need the name develop
at all. If we like, we can delete it entirely (git branch -D develop
). There's one slight danger, and that's that if we do delete it, we can't find the actual hash IDs of commits J
and K
in the future. If we decide we want original-J and original-K again, we should perhaps keep the name around (or add another name instead of develop
, or rename develop
to some other name, or whatever).
8Or, better yet, you can actually try this out.
I hear and I forget.
I see and I remember.
I do, and I learn.
—possibly, but probably not, a distorted Chinese proverb
If you do try it out, you might want to do it in another clone. You can clone your own clone. Just remember that your branch names will become remote-tracking names in the new clone, when you do this; your existing remote-tracking names in the existing repository won't get copied at all into the clone.
An easier way: en-masse cherry-pick
Rather than manually finding the hash IDs of commits J
and K
, we can just have git cherry-pick
itself do the work, by running:
git cherry-pick origin/develop..develop
This form, with the two dots in it, is how we express, in general, to Git, the idea of finding a range of commits. I won't go into any detail in this answer, which is already pretty long.
Another easier way: rebase
I won't cover this one at all here, but git rebase
consists of doing an en-masse cherry-pick operation followed by moving a branch name. If we let Git do this with our existing name develop
, then rename develop
, we can get the effect of copying the commits, then deleting the old name develop
. (To make it exactly the same, we also need to unset the upstream. The rebase needs the --onto
flag to be able to do the right thing.)
Once we're done, we just git push
our new branch
Without going into the details of how git push
really works, once we have:
J'-K' <-- newname (HEAD)
/
...--G <-- origin/main
\
H--I <-- origin/develop
\
J--K <-- develop
we can just run git push -u origin newname
, assuming origin
is the name for the URL of the GitHub repository. This has our Git call up their Git and send to them our commits J'
and K'
, then ask them to create or update their name newname
to remember commit K'
.
Since they won't have a branch name newname
yet, this will create a branch named newname
. They will then have, in their repository:
J'-K' <-- newname
/
...--G <-- main
\
H--I <-- develop
Note how their names do not begin with origin/
. They have the same commits—commit hash IDs are universal across all Git repositories, everywhere—but different branch names.

- 448,244
- 59
- 642
- 775