-2

I create a feature branch 'Fb1' on server, and connect to it locally, pull the code and start coding and commit the code to the local branch.

Then I switch to another branch 'Fb2', pull code and start coding.

Meanwhile, if someone deletes the feature branch 'Fb1' on server before I pushed the code, and re-creates the Fb1 branch on the server with new code, and then I do a fetch to get my local repos in sync with server repos. Now do I still have access to my local commited Fb1 or does it get overwritte with rhe neq Fb1 branch code on the server and how do I access the Fb1 code in visual studio team explorer?

variable
  • 8,262
  • 9
  • 95
  • 215
  • 1
    Just switch to ‚Fb1‘? Or did I get you wrong? – MSpiller Oct 05 '19 at 08:54
  • So the IDE points to the code in the respective branch? – variable Oct 05 '19 at 09:31
  • If someone deletes the branch remotely it won’t affect you. Unless you prune. – evolutionxbox Oct 05 '19 at 10:46
  • Ok yes. But when I switch to another branch, i can no longer see the Fb1 branch code. How to commit the code from Fb1 into new branch say Fb3 that also exists on server. Or do i have to copy it to notepad file to then paste it in code when i connect to Fb3? – variable Oct 05 '19 at 15:50
  • You can simply merge the code. E.g. switch to Fb3, then call „git merge Fb1“ Same as you would do for any other branch or changes. – MSpiller Oct 05 '19 at 16:12
  • How can we do this in visual studio any idea? – variable Oct 06 '19 at 05:31
  • Go to team explorer and click on merge? – MSpiller Oct 06 '19 at 19:22
  • So merge is always either among local branches or server level branches. And never across the local and server. Right? – variable Oct 06 '19 at 19:32
  • Merge is a operation between branches. No matter whether local or „server“. All operations you do are „local“. You can decide anytime to provide your changes to your colleagues by doing a push. – MSpiller Oct 07 '19 at 09:11

3 Answers3

1

Your local branch will not be affected, you will still be able to run :

git checkout Fb1
  • If you want to re-create a remote branch named Fb1 :

    git push origin -u Fb1
    
  • If you want to push this to a new remote branch named Fb3 :

    # with this command : your local 'Fb1' branch will be linked to remote 'Fb3'
    git push origin -u Fb1:Fb3
    

    or probably clearer :

    # rename your  local 'Fb1' branch to 'Fb3' :
    git checkout Fb1
    git branch -m Fb3
    
    # push this 'Fb3' branch to remote :
    git push origin -u Fb3
    
LeGEC
  • 46,477
  • 5
  • 57
  • 104
0

I think you just need to switch back to your Fb1 branch. You should find it as you left it. Even if it has been deleted on server you can still access it locally.

  • Ok yes. But when I switch to another branch, i can no longer see the Fb1 branch code. How to commit the code from Fb1 into new branch say Fb3 that also exists on server. Or do i have to copy it to notepad file to then paste it in code when i connect to Fb3? – variable Oct 05 '19 at 15:49
0

There are several things happening all more or less simultaneously, which can be pretty confusing. The way to keep this all straight is to start with this notion: Git is not about files. Git is about commits. Commits contain files, so as a result, Git will store files; but what Git cares about are the commits, with their big ugly hash IDs.

You've seen commit hash IDs in git log output, probably. They look like bc12974a897308fd3254cf0cc90319078fe45eea, for instance—this is a commit in the Git repository for Git itself. Every commit gets its own unique hash ID. No other commit anywhere, ever, can use that hash ID. Git took that one for itself and now it's that commit. All of your commits will have different hash IDs.1 Every Git everywhere automatically agrees with every other Git everywhere what the hash ID is for each commit once it's made, and no commit hash ID ever changes—so bc12974a8973... is always, forever, that particular commit in the Git repository for Git.

Branch names, like fb1 or fb3,2 come into the picture because mere humans can't remember, or type in, big ugly hash IDs. I had to cut-and-paste that one to get it right. These names let you—and Git—find the hash IDs.

You can create and destroy branches at any time. To create a branch, you pick out some commit—some hash ID, which you can find by some other name or by running git log and cutting and pasting a raw hash ID—and tell Git: Make a new name, and in that name, store this here raw hash ID:

git branch gitty bc12974a897308fd3254cf0cc90319078fe45eea

assigns the above hash ID to the name gitty. (Your Git will ensure that this hash ID actually exists in your repository, and is the hash ID of a commit.3) If you use another name:

git branch gitty master

Git first turns the name master into the raw hash ID, then puts the raw hash ID into the new name gitty.

To see what hash ID any name represents at the moment, use git rev-parse:

$ git rev-parse master
bc12974a897308fd3254cf0cc90319078fe45eea

That's how I got the big ugly hash ID above in the first place.


1Technically, it is allowed to re-use hash IDs, as long as the two Gits with re-used IDs never meet each other. So if you never try to combine a Git repository for Git with your own Git repository for whatever it is you're doing, your Git could re-use that hash ID. The chance of an actual hash collision is extremely remote, except in cases in which someone deliberately forces a collision. See also How does the newly found SHA-1 collision affect Git?

2I put these in all lowercase. In general, it's unwise to mix upper and lower case names for branch and file names, if you want your repository to work right on most MacOS and Windows systems.

3Git uses hash IDs for more than just commits. They also identify the specific contents of specific versions of files, for instance. Hence when you make thousands of commits that mostly re-use most of the files, the new commits just refer back to the existing, already-committed files.


Draw a picture

Even though these hash IDs are really concrete, they're not useful for humans: we just can't keep them all straight. Again, that's why we use branch names. But inside Git, Git is using the hash IDs. This will make more sense in your own head if you draw pictures.

Remember that each commit holds a snapshot—a set of files, as of however they looked at the time you made that commit—but it also holds some extra information, which Git calls metadata. This metadata includes the name and email address of the person who made the commit, and the date-and-time stamp for the commit. It includes the log message: click on the link to the GitHub copy of that Git commit and you'll see that its subject line is Fourth batch (and the rest is just a signoff line—not the best commit message ever, but it could be worse4). Perhaps the most important part of commit metadata, though, is that each commit holds the raw hash IDs of its parent commits.

When one thing holds the hash ID of another commit, we say that the first thing points to the second commit. So if the name fb1 holds the hash ID of a commit, the branch name fb1 points to the commit. That commit itself holds the hash ID of some parent commit. The parent commit holds the hash ID of its parent, and so on:

... <--o  <--o  <--o   <--fb1

where each round o represents a commit.

These arrows all go only one way—backwards—so by definition, a branch name points to the last commit in a chain of commits. The commits themselves have the internal backwards arrows that link them together.

When you make a branch and start committing, what Git does is make the new commits point back to the older commits. Then it moves the branch name forward so that it points to the new commit. Let's draw that, using single uppercase letters to stand in for commit hash IDs (since hash IDs are too big and ugly):

...-F--G--H   <-- master, fb1 (HEAD)

I'll describe what the word (HEAD) is doing here in just a moment. Here, for my convenience, I've drawn all the internal arrows as just connecting lines. We can just keep in the backs of our minds that they point backwards; most of the time we don't really have to care.

We've just created fb1 from master, so both names point to the same commit, the one labeled H (for hash ID) here. Note that all the commits are on both branches: this is a weird thing that Git does, that other version control systems don't do. Commits are on more than one branch at a time.

Now let's make a new commit, which we'll call I. We do the usual mess-with-some-files step, run git add on them, and run git commit and supply Git with a log message. Git packages up a new snapshot (of all files, not just the ones we changed) and sets the new commit's parent to be H, so that new commit I points back to H:

...-F--G--H   <-- master, fb1 (HEAD)
           \
            I

Now Git updates a branch name to point to I. Which branch name does Git update? That's where HEAD comes in. HEAD tells Git which branch we have checked out. We have fb1 checked out, and hence we had commit H checked out. Now that Git has created new commit I, Git updates the name to which HEAD is attached, so that we get:

...-F--G--H   <-- master
           \
            I   <-- fb1 (HEAD)

We've now created a new commit on the branch fb1. Commits up through H are still on both branches. Commit I, the new one, is only on fb1.

If we make more commits, those too are only on fb1:

...-F--G--H   <-- master
           \
            I--J   <-- fb1 (HEAD)

4In the xkcd comic, newer commits are towards the bottom. In git log --graph --oneline output, newer commits are towards the top. In the way I draw commit graph fragments on StackOverflow, newer commits are towards the right. Graph drawings can be oriented any way that helps you view them, so pick whatever way suits you.


Branches are great, let's make more!

Now that we have fb1 with a couple of commits on it, let's go back to master and make a new branch fb2. We can do that in any number of ways, including:

git checkout master
git branch fb2
git checkout fb2

or more simply:

git checkout -b fb2 master

which does all of that in one step. The result is that our graph doesn't change at all, but we add a new name, fb2, and attach HEAD there:

...-F--G--H   <-- master, fb2 (HEAD)
           \
            I--J   <-- fb1

We now have commit H checked out, through the new name fb2. If we run git log, we'll see commit H, then commit G, then commit F, and so on: git log starts where we are, then follows the internal arrows backwards.

Note that commits I and J are still in our repository, and we can still find them: the name fb1 finds commit J, and from commit J, we can step back across the internal, backwards-pointing arrow to find I. A plain git log won't show these two commits: we have to run git log fb1 to see them, so that git log will start with commit J as identified by the name fb1, rather than starting with HEAD and therefore finding H.

Now that we're in this new position, let's make two new commits. They'll get their own unique, big ugly hash IDs, but we'll just call them K and L. Let's draw them—and for convenience let's flip fb1 to the top row:

            I--J   <-- fb1
           /
...-F--G--H   <-- master
           \
            K--L   <-- fb2 (HEAD)

We now have three branches in our own Git repository, found by the three names fb1, fb2, and master. These three branches find three commits, but those three commits themselves each find more commits: Git starts with whichever commit we tell it, then walks backwards, following the internal, backwards-pointing arrows.

Because commit H is the parent of both I and K, we find commit H three times. We find its parent, G, three times too, and so on for all the commits behind it. All of these commits are on all three branches now. But commits I-J are only on branch fb1, and commits K-L are only on branch fb2.

Deleting names

Suppose we were to delete the name master entirely, using git branch -d master:

            I--J   <-- fb1
           /
...-F--G--H
           \
            K--L   <-- fb2 (HEAD)

We now have no branch named master. But we still have all of its commits. They're still easy to find: we can start at either fb1 (commit J) or fb2 (commit L) and walk back to H, and there's H. So all of these commits are still there, still on two branches. They were on three branches before; now they're on two.

We can put master back, using git branch master <hash-of-H> (cutting and pasting the hash ID from git log), then tell Git to delete the name fb1. We can't, right now anyway, delete fb2 at all because we're standing on it (HEAD is attached to fb2). We would have to tell Git to force the deletion of fb1, because look what happens if we did:

            I--J   ???
           /
...-F--G--H   <-- master
           \
            K--L   <-- fb2 (HEAD)

Once the name fb1 is gone, how will we ever find commit J? We depend on finding commit J to find commit I, too. We can still find L and K and H and so on, but if we delete the name fb1, we lose the hash ID for the last commit in that branch.

Since Git is all about commits, Git won't let us delete fb1 without forcing it, and of course, we shouldn't. However, if fb1 still pointed to H—so that we could find commit H via fb2—Git would let us delete fb1 without forcing it.

Using another Git: git fetch

All of the above describes how we use our Git repository, locally. But in your case, you have another Git repository, stored over on GitHub. Your Git calls this other Git origin—that's basically a short name for the URL you use to have your Git talk to the Git over on GitHub.

You can run:

git fetch origin

to have your Git call up their Git. Your Git uses the URL stored in the name origin—Git calls this thing a remote and it's just a short name for a URL and some other useful data—to phone up their Git, and your Git and their Git have a conversation. Their Git lists all their branch names, and their raw commit hash IDs. Your Git checks to see if you already have these commits, or not. If you don't have the commits—remember, the commit hash IDs are universal across every Git, so your Git can just check by ID—your Git will then ask their Git to package up those commits. By extension, your Git will get all of those commits' files, and their parent commits and their files, and so on. Your Git and their Git talk with each other to figure out which commits you already have, and therefore don't need, so that they only send you the stuff you need.

Having sent you all those new commits—whatever their hash IDs are—your Git now has them, stored. But your Git needs to have some sort of name for the last new commit in any updated branch. This is where your remote-tracking names come in.

Let's say, for instance, that they've added a commit to their master. We'll call this commit M:

            I--J   <-- fb1
           /
...-F--G--H   <-- master
           \
            \--M   <-- ???
             \
              K--L   <-- fb2 (HEAD)

What name is your Git going to use to remember their master? The answer here is that your Git creates your own name origin/master, in a separate name-space,5 so that we get:

            I--J   <-- fb1
           /
...-F--G--H   <-- master
           \
            \--M   <-- origin/master
             \
              K--L   <-- fb2 (HEAD)

In fact, you already had an origin/master pointing to H before. We just didn't draw it in!

Now, if you created an fb1 in the Git over at origin and you've done a git fetch, you'll also get an origin/fb1. This will point to whichever commit the fb1 on the GitHub Git points to.

If someone else, who can also manipulate the GitHub Git, moves or deletes their fb1, your Git still has your own fb1. That never goes away on its own: it is your branch, and you control it. Your origin/fb1 might move, and if you set your Git up to prune remote-tracking names, your origin/fb1 might go away entirely. But your fb1, your branch, is under your control. You choose how to move it or delete it.


5The full name of a branch in your repository starts with refs/heads/. The full name of the remote-tracking name origin/master is refs/remotes/origin/master. This doesn't start with refs/heads/, so you can create your own local branch name, refs/heads/origin/master, which is separate from your remote-tracking name.

Don't do this! It won't break Git but it might break you. :-) If you have accidentally done this, rename your local branch so that its name does not start with origin/ any more, so as to de-confuse things.


What about git push?

The git push command is basically the opposite of git fetch: you have your Git call up their Git, and then instead of getting new commits from them, you give new commits to them.

It's not completely opposite though. When you get new commits from them, your Git automatically creates or updates your remote-tracking names, so that you have the origin/* names by which to find their new commits. But when you git push commits to them, you ask them to set their branch names.

So if you git push commits I and J, for instance, you will almost certainly want to ask them to set their branch name fb1 to match yours.

Let's say we have commit M and we've already updated our own master to include it. They have fb1 pointing to commit H, and don't have fb2 at all yet, so that we have, in our repository, this graph:

            I--J   <-- fb1
           /
...-F--G--H--M   <-- master, origin/master, origin/fb1
           \
            K--L   <-- fb2 (HEAD)

We can now run:

git push origin fb1

This will send commits I and J to them: they don't have them, but they need them in order to move their fb1 to point to J. Then our Git will ask their Git: Please, if it's OK, set your fb1 now to point to commit J.

In this case it will be OK. They will set their fb1 to point to J. Your Git will acknowledge this by updating your own origin/fb1, and now you have:

            I--J   <-- fb1, origin/fb1
           /
...-F--G--H--M   <-- master, origin/master
           \
            K--L   <-- fb2 (HEAD)

in your repository. They just have master identifying M and fb1 identifying J; they don't have fb2 at all yet.

What about git pull?

The git pull command is just shorthand for:

  • run git fetch;
  • then run a second Git command, usually git merge, but you can choose git rebase.

I advise newcomers to Git to avoid git pull because it hides this two-step process, and leaves them stranded when the second command fails for some reason (which can and does happen).

Merge is a complicated command, with complicated failure modes.

Rebase is in some sense worse: in effect, it is a repeated series of git cherry-pick commands (followed by one branch-label move), and each one is a form of merge. So if merge can fail, so can each cherry-pick. If there are five commits to cherry-pick to accomplish the rebase, there are five steps that can fail!

Most merges actually do succeed on their own, and a significant number of rebases work without problem. But if one does fail, it's important to know which one you were doing, so that you know where to look for help.

It's OK to use git pull—as a convenience instead of git fetch followed by whichever second command—but just remember that that's what it is, so that when the second command fails, you know which second command you used, and what to do next. Even knowing all this, I mostly don't use git pull myself, because I often like to run git log between the git fetch and the second command, to look at what git fetch fetched. Sometimes, that changes whether I use git merge or git rebase!

Conclusion

With all of the above in mind, let's look at the subject line question:

What happens to local committed code if git branch is deleted on server?

Nothing. Nothing at all happens to local commits. Your local branches are yours, to do with as you will. You choose what happens next.

torek
  • 448,244
  • 59
  • 642
  • 775