George M Ceaser Jr
George M Ceaser Jr

Reputation: 1781

I am really confused about Git Branches

I am coming from a TFS background with everything stored in SQL Server. I am now in a situation of using GIT through DevOps for managing code. There are several things that I am very confused about.

So lets say I go into DevOps via the web browser and I see the default branch (i.e. Master). Now I go into Visual Studio 2019 and I say new branch based on Master and I give it the name QA_6.5.3_George.

The branch appears to be created and I have the code but I don't see that branch in DevOps. When you create a branch like this is it only local until you push it up?

Then, supposed I wanted to clean up branched I created:

I click on the Git Repository Explore in Visual Studio and I see the following:

enter image description here

I only want to delete my local branches. How do I know which of these are branches I created and which are not mine? I just find this very confusing. I have found documentation on HOW to delete branches etc. I just don't understand what is going on under the covers with Git regarding 'ownership' and location of branches.

FYI I am looking to understand this in the context of Azure DevOps and Visual Studio 2019

Any help would be greatly appreciated.

UPDATE: So here is an actual example of what I am trying to understand:

Here is my DevOps server showing I have no branches for a project other than the master:

enter image description here

However in Visual Studio I see I have a branch based on 6.5.2.

enter image description here

What I am trying to understand is why I don't see my QA_6_5_3_George branch in DevOps?

Upvotes: 1

Views: 616

Answers (1)

torek
torek

Reputation: 488183

Here, I think, is where your confusion comes in: Git is a distributed version control system in which every user gets a copy of every repository. A Git repository consists mainly1 of two databases, one usually much larger than the other:

  • The larger database holds commits and other internal Git objects. These objects are numbered: each one has a very large, random-looking number, expressed in hexadecimal, which Git calls an object ID or less formally, a hash ID. These numbers are the same in every copy of the repository: that is, if your copy has an object with hash ID 9c897eef06347cc5a3eb07c3ae409970ab1052c8, and some other repository has an object with that same hash ID, those are the same object.

    Git needs these numbers to access the objects. But obviously it's hard to memorize 9c897eef06347cc5a3eb03c3ae409970ab1052c8. Is that even the same number I quoted just a moment ago? (It's not. See if you can see which character I changed.) Therefore, each repository carries a second database: a table that maps names to hash IDs.

  • The smaller database is this names table. In here you'll find your branch names, your tag names, and your remote-tracking names, among other names. Each name maps to just one hash ID: this means that you can type in a branch name, like master or main or QA_6.5.3_George and have Git itself look up the right hash ID.

By using names instead of numbers, you can keep yourself from having to memorize hash IDs. But here's where we have to be careful: a branch name is not a branch, except when the person saying branch means branch name. (For a humorous way to remember the difference between a thing and its name, see the Wikipedia article on Haddock's Eyes. For more on the distinction in Git, see What exactly do we mean by "branch"?)

When you clone a repository, you get all of the commits (and supporting objects), and you get no branch names at all. Then, having cloned the repository, your Git software will create one branch name in your repository. You can use this branch name, or any of the remote-tracking names, to create more branch names; or you can use any Git commit hash ID to create a branch name. The hash IDs are what Git cares about. The branch names exist for your purposes.

Again, the cloning process copies only the commits (and other Git objects) database, not the names database. This means that all of your branch names are local. Even your remote-tracking names are local: they're your software-and-repository local memory of some other repository's branch names.

When you run git push, you have your Git software connect to some other Git software. Your Git software reads from your Git repository. Their software reads from, and potentially writes to, their repository. Your Git can see their branch names (and other names) here and their hash IDs, and your Git can offer commits to their Git.

Your Git—your software working with your repository—will now offer, to their Git (their software working with their repository), any new commits you have made, that they do not yet have. This all works by hash ID. Because the hash IDs are unique, and match if and only if the two repositories have the same actual commits, your Git can easily tell if some commit that you have is or is not the same as some commit they have. So any new commits you made can be sent over, without having to re-send the existing commits.

Once your Git is done sending over your new commits (as needed), your Git now asks their Git to create or update one of its branch names in its branch-and-other-names database. If they obey this polite request, your Git will also create or update your corresponding remote-tracking name.

For instance, suppose you create a new-to-you branch-name george-ceaser. Let's assume further that they do not have this same branch name.

Whether or not you make any new commits on this branch, you then run git push origin george-ceaser so that your Git calls up their Git and:

  • sends any new commits you made (if any), then
  • asks them to create or update a branch named george-ceaser in their Git.

If they obey this polite request, they now have a branch-name george-ceaser, and your Git will create origin/george-ceaser to remember the same hash ID that you just had them remember under the name george-ceaser. Now you and they have "the same branch", as humans will put it—but in fact, they have their own name george-ceaser, and you have your own name george-ceaser. These are two different names that merely happen to be spelled the same!

You may delete your george-ceaser at any time. Once you're done with it, you should delete it. That won't affect their george-ceaser at all: that one is their name. Should you wish to ask them to delete their george-ceaser, you would do this with:

git push origin --delete george-ceaser

which sends to them a polite request that they delete their branch name george-ceaser. If they obey this polite request, nothing happens in your Git. If they refuse this polite request, nothing happens in your Git.

(Again, by "your Git" I mean "your software operating on your repository".)

Now and then, it's wise to run git fetch origin --prune. This has your Git call up their Git, list out all their branch names, and then—because of the --prune—your Git will remove from your repository any origin/* name that exists in your repository, but for which their branch name is now gone.2 Usually though, you'll know whether you've made george-ceaser in their repository, and if and when you delete the name george-ceaser in your own repository, you will know whether you want to ask them to delete the name george-ceaser in their repository.

You will find this confusing for some time. It is fundamentally confusing to humans, who don't handle clones very well. We think clones are exact copies, but they aren't; then we make changes to the copies, and we can't remember which copy we changed; and then we get mixed up about who has what and it gets messy. Just remember that the idea behind Git is that everyone gets a copy. You make changes to your copy, after which you may (but are not required to) use git push to send your new commits to some other copy, and then ask them to create or update one of their branch names to remember your new commits.

In some cases, you may end up using git push --force-with-lease or git push --force to change the last step of git push from "polite request" to "forceful command". That is, instead of asking the other Git Please, if it's OK, create or update your george-ceaser you can send I command you to create or update your george-ceaser! They're allowed to reject the command, but they're more likely to obey a command, even if it's harmful, than they are to obey a polite request even if it's harmful. So be (much) more careful when using --force or --force-with-lease. Git tries harder not to do anything harmful when you use the polite-request form.


1Most repositories also offer a working area, and the repositories you use will be like this.

2I personally like this behavior enough to set fetch.prune to true, so that every git fetch does it, but there are a few sharp edges here and there in Git where this may unexpectedly delete some remote-tracking name, so you might want to do it only at carefully-chosen times until you're much more familiar with this.

Upvotes: 1

Related Questions