I want to find all branches from a master branch (not repo)
This does not make sense, because branches do not branch from branches.
... and be able to check if any of those branches are pushed (after some changes) or not.
This question could make sense, if rephrased somewhat.
The key to both of these is to understand what branches are, or are not. But before you can do that, you have to realize that not everyone means the same thing by the word branch. In fact, someone can say the word branch more than once in the same sentence and mean two or more different things.
(Related: What exactly do we mean by "branch"?)
What really matters, in any repository, is not the branches. The commits are what matter. Branches are just how you find commits. In this particular case, what I mean by the word branch is a branch name, such as master
or develop
. These names are specific to this one repository. A clone of this repository, in some other Git, perhaps on some other computer or on some cloud-server or whatever, has its own branch names, independent of your own.
When you connect two Git repositories to each other, using git fetch
or git push
, one Git sends commits to the other Git, which receives them. The receiving Git can see some or all of the sending Git's names (branch names, tag names, and other names), but what really matters are the commits. Having sent or received some commits, though, we're left with the problem of finding the commits.
Every commit has a unique hash ID. This hash ID is big and ugly and impossible for humans to remember. Fortunately, each commit remembers some set of previous commit hash IDs—usually exactly one. Git calls this the parent commit. The child commit remembers the hash ID of its parent. When you make a new commit, Git assigns the new commit a new, unique hash ID, and puts the hash ID of the commit you were using, just now, into the new child commit as the child's parent. (And now you're using the child commit.)
Of course, that parent commit is probably itself the child of some previous commit. So the parent remembers its parent—the grandparent of the child you just created—and that commit remembers its parent, and so on. The result is a long, backwards-pointing chain, in which the last commit is perhaps the most interesting:
... <-F <-G <-H
Here H
stands for the hash ID of the last commit. Because H
holds the hash ID of its parent G
, we can use H
to find G
. Meanwhile, G
holds the hash ID of its parent F
, so we can use G
to find F
, and so on. These backwards-pointing arrows mean that we only need to have Git remember for us the hash ID of the last commit in the chain.
This is what branch names are for. They hold the hash ID of the last commit in the chain:
...--F--G--H <-- master, branch2, branch3
Note that here, all three names identify commit H
.
If we git checkout master
and then make a new commit, it will get some big ugly hash ID that we'll call I
. New commit I
will point back to existing commit H
as its parent:
...--F--G--H
\
I
and now, because we picked master
as our branch to git checkout
, Git will update the name master
to hold the hash ID of new commit I
:
...--F--G--H <-- branch2, branch3
\
I <-- master (HEAD)
The attached (HEAD)
is the way for us (and Git) to know which branch name to move when we make new commits. The other two branch names—branch2
and branch3
—have not changed. If we git checkout branch3
we get:
...--F--G--H <-- branch2, branch3 (HEAD)
\
I <-- master
and if we now make a new commit, we get:
J <-- branch3 (HEAD)
/
...--F--G--H <-- branch2
\
I <-- master
That's almost all there is to it: branch names are just pointers, pointing to commits.
If we have our Git call up some other Git over the Internet-phone, our Git can tell their Git: Hey, I have commit I
, do you have it? If they say no, our Git can give them commit I
. All Gits in the universe will agree that commit I
gets commit I
's hash ID, and no other commit gets this hash ID. So they just have to exchange the hash IDs first: the actual contents of the commit—the snapshot of all files—can go later if needed (and can be compressed down to just what the other Git really needs), and it's just the hash IDs that matter here.
Once we have given them I
—and H
too if they need that, and G
too if needed— they may have something that looks like this:
...--F <-- master
\
G--H--I
That is, they have a name, master
, pointing to their existing commit F
, which has the same hash ID as our existing F
and is therefore the same commit with the same files. And now, they have G-H-I
too, with I
pointing back to H
, H
pointing back to G
, and G
pointing back to F
. But they have no name by which to find commit I
.
So, our Git, having sent them commit I
(and any earlier commits required), will now send them a polite request: Please, if it's OK, change your name master
to point to commit I
. It is up to them to decide whether or not to obey this polite request. If they do obey, they will stuff the raw hash ID—whatever that is—of commit I
into their name master
.
So:
... be able to check if any of those branches are pushed
This still isn't a sensible thing to do as phrased. But what we can do is call them—this other Git—up, ask them about the hash ID in their name master
, and compare it to the hash ID in our name master
. Are these the same hash IDs? If so, we're in sync. If not, we're not.
Exactly how we're out of sync, we won't know. We'll just know if we are in sync or not. That's probably the question you wanted here. (If you want to know exactly how we're out of sync, if we are out of sync, that's a more difficult question.)
So, to answer this new and different question, we should call up their Git, have them list their branch names and contained raw hash IDs, and compare those to our branch names and contained raw hash IDs. They'll match, or not; or perhaps we'll have branch names they don't and vice versa.
Before you do any of this, though, consider one last feature of git fetch
(implemented by Git anyway: this may or may not be in your Python library, depending on how precisely it mimics Git or if it uses Git directly). I can use git fetch
to have my Git connect to your Git:
git remote add my-name-for-you <url-for-your-git>
git fetch my-name-for-you
When I do this, your Git tells my Git all of its branch and tag names. My Git then lets me pick which names I like—the default is that I like all of them—and it gets the last commit from each of your branch names from you, and any earlier commits I need as well, so that I have all of your commits. Then, in my Git, it creates or updates remote-tracking names for each of your branch names:
- my
my-name-for-you/master
holds the hash ID that your master
holds;
- my
my-name-for-you/develop
holds the hash ID that your develop
holds;
- ... and so on, for every branch name you have.
So instead of calling your Git up again every time, I can just use my Git's memory of your Git's branch names.
If my Git's memory is out of date, I just run git fetch my-name-for-you
. My Git calls up your Git, updates my memory of your names, and obtains all of your commits.
If I'm giving you commits—if I run git push my-name-for-you master
—I'll send you the commits and ask your Git to set your master
. Your Git will either obey, or say no and tell me a little bit about why it said no. If your Git obeys, my Git will update my my-name-for-you/master
to remember that your master
now stores the same hash ID I just sent you.
So, in general, instead of connecting to some other Git, you just inspect the hash IDs of your own origin/*
names. The name origin
is the default name for the default remote, created when you first made your Git repository by cloning some other Git repository. If necessary, you can run git fetch origin
before checking the origin/*
names.
(For some special tool purposes, it may sometimes be better to use git ls-remote
instead of git fetch
. This obtains the names and hash IDs—which is the first step of an actual git fetch
—but then just prints them out and stops, instead of going on to do the rest of the git fetch
work. The downside is that you'll probably eventually need to git fetch
, but the upside is that you get a picture that's accurate for the moment, without waiting for git fetch
to work. This moment may not be very long, depending on how active the other Git is.)