text: Q:
  
  What does justify the difference between my local code and my branches differences?
  
  I had a strange situation today and I still can't understand the reason why git had this behavior.
  I had a branch A that I merged with master.
  During the merge branch A got deleted:
  git merge --no-ff branch A
  git branch -D branch A
  
  I reverted master to its state before branch A merge and recreated branch A under the name branch B and reset it to its last commit:
  git revert merge_commit_id -m 1
  git checkout -b branch B
  git reset --hard branch_A_commit_id
  
  Now with that steps I had on my local:
  
  master as it was before everything and when I tested on my local environment everything was as before.
  branch B had the changes as branch A was before everything, when testing on my local environment it was as before however nothing was different from master for Git.
  
  I added some changes in branch B and pushed them to the distant repository and only the newly added changes differed from master, even though everything was different on my local environment (I had all changes from branch A and branch B).
  So I had all my changes locally in branch B, I could not see them in git status, nor commit or push them since Git behaves as if they weren't different from master, both my distant and local branches were synchronized and up to date.
  Doing git pull origin branch B didn't change my code in local. Pushing wouldn't do anything either.
  I got my changes back by a workaround, but I would like to understand what happened here for Git and what would be the best way to get my old branch A back if such a situation happens again?
  My best guess is that originally branch A got deleted during the merge so the commit I used to reset my branch B was actually referring to an old version of master which could explain the behavior since Git would assume my changes are an old version of master and not actual changes from branch A.
  
  A:
  
  In this case, there are two Git repositories involved.  One Git repository is on Bitbucket, and one is on your local machine (git clone url-of-bitbucket-repo produces a second Git repository).  The two repositories will share commits, as identified by commit hash IDs, but each Git repository has its own set of branch names.  What you'll see in branches therefore depends on which repository you look at.
  Basics
  Before we get to the process, or "what happened", let's start with these bullet point items (many of which may be familiar to you—if they are, feel free to skip forward to the next section):
  
  Commits are actually identified by their unique hash IDs.  While every commit gets its own unique hash ID, different from every other commit, every Git also computes the hash ID the same way.  So every separate Git repository uses the same hash IDs for the same commits.  This means two Gits can easily tell whether they have each other's commits by comparing hash IDs.
  (To make this work, no part of any commit can ever be changed once it's made.  The hash ID is based on all of the data-and-metadata in the commit, and if even one bit in one file, or one letter in the log message, were to change, that would invalidate the hash ID.  If you try to change a commit, what you end up doing is making a new commit with a new and different hash ID.  The old commit still exists!)
  Each commit stores a full snapshot of all files.  You can only view a snapshot as "changes to some files" by comparing that snapshot to some other snapshot.
  Each commit also stores some metadata: the name and email address of the commit's author, and a date-and-time stamp, for instance.  (There are actually two of these, one for "author" and one for "committer".)  Your commit log message is part of this as well.
  Each commit also stores the hash ID(s) of its immediate predecessor commits, which Git calls the parents of that commit.  (This is also part of the metadata.)
  Branch names just hold the hash ID of one single commit.  That commit is the one that should be treated as the last commit in the branch.
  In general, to make a new commit, you—or Git—come up with a new snapshot of files, write out that snapshot, add the appropriate metadata, and create a new commit.  This does not affect any existing commits in any way: the new commit gets a new, unique hash ID.  The new commit's parent commit(s) must be the hash IDs of some existing, valid commits in this repository.
  Since a branch name is the last commit in the branch, adding a new commit has Git update the current branch name—the branch you checked out with git checkout—so that it now identifies the new commit.  The new commit's parent is the commit you had checked out just a moment ago.
  This all works because the new commit refers back to the existing commit, which refers back to its parent, and so on.  We add commits to the branch by making sure the new commit remembers its parent.  If the new commit didn't remember its parent, and we were to set the branch name to remember the new commit, the previous commit would become, at best, very hard to find, because hash IDs are so random-looking.
  When you have two repositories, the commits get shared via git fetch and git push.  Note that git pull runs git fetch followed by a second Git command, usually git merge, on the theory that if you've just obtained some new commits from their Git, you probably want to integrate those commits into your branch name(s) somehow.  Your branch names remain yours, and git fetch itself does not touch them at all.  It's the second command that touches one of them.
  In order for your Git to remember the hash IDs that their Git calls "the last commit in the branch", the git fetch step first obtains any new commits from some other repository, then creates or updates your remote-tracking names for that repository.  If your Git is calling their Git origin, for instance, and they have a branch master, your Git will use your origin/master to remember what their Git says their master is.
  In other words, a fetch ends by setting your Git's remote-tracking names: your origin/master might change, for instance, but your own master never changes.  Your origin/branch-A might be created, or changed, or even deleted, but your branch-A never changes.
  Besides the direction of transfer, git push is very different from git fetch in one very important way.  At the end of git push, your Git requests, or even commands (git push --force), that their Git should set one of their branch names.  You don't have them set their remote-tracking names!  They may not even have remote-tracking names: it's relatively uncommon for server-side bare repositories to have them.
  
  Drawing commit graph fragments and doing a merge
  As we noted above, each commit stores the hash ID(s) of its immediate predecessors.  We, or Git, can use this to group our commits into a graph.  (This is more specifically a Directed Acyclic Graph or DAG, though we won't go into any more detail here.)  We find the last commit's hash ID—let's call it H—by reading a branch name like master.  The name contains the hash ID, so we say that master points to commit H:
                H   <-- master
  
  But H itself contains a commit ID too: the hash ID of H's parent.  Let's call that G.  H then points to G.  Of course G also points to something:
          <-G <-H   <-- master
  
  Let's call the commit to which G points, F:
  ... <-F <-G <-H   <-- master
  
  This repeats all the way back to the very first commit, which—being the first commit—doesn't point anywhere.  Git calls this a root commit, and it's where, when following commits backwards like this, we have to stop.  (We usually stop earlier, when we get tired. :-) )
  This backwards-pointing chain, built out of the commits, forms the commit graph.  It also means that we can define an "is an ancestor" test for any given pair of commits.  G is the parent of H, for instance, so G is an ancestor of H.  Meanwhile F is the parent of G, so F is also an ancestor of H.
  Once we start adding new commits, though ... well, look at this partial graph drawing:
            I--J   <-- branch1
           /
  ...--G--H
           \
            K--L   <-- branch2
  
  Here, the name branch1 identifies commit J, whose parent is I, whose parent is H.  So H is an ancestor of J.  The name branch2 identifies commit L, whose parent is K, whose parent is H.  So H is also an ancestor of L.  But I is not an ancestor of L.  I is not a descendant of L either.
  To achieve a merge, we pick one of these two branches—git checkout branch1, for instance—and then run merge on the other: git merge branch2.  Git will:
  
  find the best common ancestor (H), which Git calls the merge base;
  figure out what changed in branch1 by comparing the merge base to the tip commit of branch1, i.e., J;
  figure out what changed in branch2 by comparing the merge base to the tip of branch2, i.e., L; and
  combine the changes, applying the combined changes to the snapshot in H, to make a new merge commit M, that has both tip commits as its two parents.
  
  This gives us:
            I--J
           /    \
  ...--G--H      M   <-- branch1 (HEAD)
           \    /
            K--L   <-- branch2
  
  as our result.  (The name branch1 is the one that got updated, because we checked it out.  To show that it's the branch we have checked out, I added (HEAD) to the branch label.)  Working backwards from a merge commit like M is a matter of going to both of its parents, so git log from branch1 will show commit M, then all four commits I+J and K+L (in some order), and also commits H and earlier.
  Note that in some cases, a merge can be done trivially:
  ...--G--H   <-- branch1 (HEAD)
           \
            I--J   <-- branch2
  
  Here, git merge branch2 does not have to do a real merge.  The merge base commit H is the tip of branch1, and comparing H to H will always show no changes.  The result of combining nothing with something is always the "something", so the final result will always match commit J.  Git can be lazy and simply move the name branch1 forward and check out commit J:
  ...--G--H
           \
            I--J   <-- branch1 (HEAD), branch2
  
  Git calls this a fast-forward merge, even though there's no actual merging: it's really just a git checkout that drags the branch name along instead of switching to the other branch.
  You can force a real merge, even in a fast-forward-y situation, using git merge --no-ff.  The result looks like this:
  ...--G--H------K   <-- branch1 (HEAD)
           \    /
            I--J   <-- branch2
  
  where commit K is the new merge commit.
  In all three cases, we could delete the name branch2 safely afterward, because we can find whichever commit was the tip of branch2 by looking at the tip of branch1 and—if necessary—working backwards.
  This is presumably what happened during your merge
  Presumably Bitbucket's web interface for doing a merge is similar to GitHub's (I haven't used Bitbuckets).  You pick one branch, such as master, as the one that will "receive" the merge, then pick another branch, e.g., branch-A, as the one to merge.  Under the cover of the web page, Bitbucket does the equivalent of git merge or git merge --no-ff.  But then, as you said, Bitbucket also deleted the name branch-A.
  Let's draw that:
            I--J   <-- master (HEAD)
           /
  ...--G--H
           \
            K--L   <-- branch-A
  
  becomes:
            I--J
           /    \
  ...--G--H      M   <-- master (HEAD)
           \    /
            K--L
  
  or maybe:
            I--J   <-- branch-A
           /
  ...--G--H   <-- master (HEAD)
  
  becomes:
            I--J
           /    \
  ...--G--H------M   <-- master (HEAD)
  
  Revert
  A git revert works by adding a new commit that "undoes" whatever happened in the commit itself.  For both cherry-pick and revert, Git compares one commit to its parent (singular), to see what happened in that commit.  Then Git tries to re-do (cherry-pick) or un-do (revert) that change against the current commit.1  If that succeeds, Git makes a new one-parent commit.
  For instance, consider a cherry-pick of commit C, while you have commit H checked out:
  ...--B--C--...   <-- other-branch
    \
     o--H   <-- current-branch (HEAD)
  
  Git will compare the snapshots in B (parent) and C (child) to see what changed in C.  Applying those same changes to the snapshot in H gets us a new commit I:
  ...--B--C--...   <-- other-branch
    \
     o--H--I   <-- current-branch (HEAD)
  
  The difference between the snapshots in H and I will be the same (except maybe for line numbers) as the difference between the snapshots in B and C.
  A revert just tries to un-apply the changes, rather than copying the changes.  In both cases, if you're using a merge commit, it has two parents, so you must select one of the two using an extra -m argument to git cherry-pick or git revert.
  
  1Technically, Git actually does a full three-way merge here, using the selected commit's parent as the merge base, the current commit as the left side of the merge, and the selected commit as the right side of the merge.  The resulting commit is an ordinary commit rather than a merge commit, though, so it records only the current commit as the new commit's (single) parent.
  
  Doing the revert
  In this case, you did a revert, so let's draw it.  But wait a second, you did the revert in your own repository, not in the one on Bitbucket!  Let's draw your repository, after you run git fetch and git merge, or git pull to do both in one shot.
  You start with:
            I--J   <-- master (HEAD)
           /
  ...--G--H
           \
            K--L   <-- branch-A
  
  You now run git fetch and acquire commit M:
  That's what you did in this case, so let's draw it:
            I--J   <-- master
           /    \
  ...--G--H      M    <-- origin/master
           \    /
            K--L   <-- branch-A
  
  The fact that branch-A is gone in the Git over at Bitbucket does not affect your branch-A.  None of your branch names change!  At most, if you had an origin/branch-A, your git fetch would remove origin/branch-A.
  If you delete your own branch-A now, that's your responsibility.  But let's say that you do, and that you also have your master fast-forward to point to commit M.  Perhaps you git checkout master and then git pull so as to gain M, update your origin/master, and then fast-forward your master all in one command, then you delete your own branch-A after seeing origin/branch-A go away:
            I--J
           /    \
  ...--G--H      M    <-- master (HEAD), origin/master
           \    /
            K--L
  
  Now you run git revert -m 1 <hash>, so let's draw the graph.  We'll stop drawing origin/master (it still points to M):
            I--J
           /    \
  ...--G--H      M--N   <-- master (HEAD)
           \    /
            K--L
  
  Here N un-does the changes brought in by the K-L chain (well, presumably—you could have undone the changes brought in by the I-J chain by using git revert -m 2).  In any case the name master now points to commit N.
  Re-creating branch-A
  Let's recap a bit:
  
  I reverted master to its state before branch A merge and recreated branch A under the name branch B and reset it to its last commit:
  git revert merge_commit_id -m 1
  git checkout -b branch B
  git reset --hard branch_A_commit_id
  
  The graph up through commit N represents your state after the git revert.  The git checkout -b branch-B step just creates a new name, branch-B, pointing to the current commit (N), and attaches HEAD to that new name:
            I--J
           /    \
  ...--G--H      M--N   <-- master, branch-B (HEAD)
           \    /
            K--L
  
  Your git reset --hard resets the index and work-tree (which we're not drawing!) and moves the name to which HEAD is attached, i.e., branch-B, to the hash ID you supply.  Presumably that's the hash ID of commit L:
            I--J
           /    \
  ...--G--H      M--N   <-- master
           \    /
            K--L   <-- branch-B (HEAD)
  
  I added some changes in branch B and pushed them to the distant repository ...
  
  Changing files in the work-tree, adding them to the index, and committing would get you a new commit:
            I--J
           /    \
  ...--G--H      M--N   <-- master
           \    /
            K--L--O   <-- branch-B (HEAD)
  
  You could then git push origin branch-B (perhaps with -u as well) to send new commit O to the other Git, and get them to create their branch name branch-B pointing to new commit O.
  
  and only the newly added changes differed from master, even though everything was different on my local environnment (I had all changes from branch A and branch B).
  
  Here's where things go off the rails: to view changes, you must compare commit O to something.  By itself, it's just a snapshot.  What are you comparing commit O to?
  
  Doing git pull origin branch-B didn't change my code in local ...
  
  Running git pull first runs git fetch, then runs git merge.  In this case you'd have your Git call up their (Bitbucket's) Git and ask about their branch-B, which identifies commit O.  You already have commit O, so no commits come over.  Your Git then updates your origin/branch-B to point to commit O, if it didn't already—it probably did—and then you have your Git run git merge <hash-of-O>.
  The current commit is commit O, so there's nothing to merge.  So it's not surprising that this does nothing.
  Conclusion
  The above might be somewhat wrong: perhaps the merge that Bitbucket made could have been a fast-forward merge (but clearly wasn't), in which case the graph drawing at the point where you have only master and have done git revert -m 1 should be:
            I--J
           /    \
  ...--G--H------M--N   <-- master (HEAD)
  
  and it's not at all clear to me which commit you used when you re-created the name branch-A (perhaps you had it point to commit H).  The rest, however, should be pretty much the same: you'd end up with:
            I--J
           /    \
  ...--G--H------M--N   <-- master
           \
            O   <-- branch-B
  
  When you view commit O, you must do so in some sort of context.  The git show command views it by comparing it to its parent H.  A snapshot viewer would look at its saved snapshot.
  In puzzling cases like this, it's always helpful to stop and draw the graph.  Be mindful of the fact that two different Git repositories can have different branch names ... and, if you have not run git fetch or git push, one repository might have some commit(s) that the other does not.  Once the commits are shared, though, so that every repository has every commit, it's all a matter of looking at the graph: find the tip commits from the branch names, and work backwards.
  
  