Search code examples
gitgithubmergephpstorm

git: Branches that don't look like branches


I've never gotten the hang of git, and I only work on programming off and on, so I forget the status of my projects. At the moment I'm not making sense of what git is telling me about old branches.

git branch -a says:

  dashboard
* master
  php7
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/php7

So it says I have two branches besides master: dashboard and php7. I remember php7 clearly - it was a big chunk of work with many commits. I don't remember dashboard, but the name tells me what file it was related to.

git branch --merged says:

  dashboard
* master
  php7

So apparently both are merged. But php7 is known on remote while dashboard is not. I don't remember what git commands I did to get to this point.

When I look at the log in PHPStorm (or with git log --graph) those two don't look like what I would expect a branch to look like - there is no diverted line with commits on it. Here is the most recent section of the log: enter image description here

Mysteries (at least to me):

  • Commits I remember doing on the php7 branch are all on the same orange line as master. If I merged and then deleted the branch (logical), why is it still listed as a branch at all?
  • Why is there an extra "origin" label on the last of the php7 branch? I would think recent pushes would cause remote to look just like local, with only one "origin" label on the latest commit. Surely remote doesn't consider php7 to be not-yet-merged - that would be horrible, as that code is critical.
  • dashboard is not known by remote at all (https://github.com/OsakaWebbie/kizunadb) - how could that be?

On the other hand, earlier in the log there is a very obvious branch that had two commits and then was merged, which is not in the list of branches: enter image description here I would be happy to delete it (I don't keep old branches for nostalgia), but git branch -d texlabels says: error: branch 'texlabels' not found. The most branchy-looking thing in the log isn't a branch? My head hurts.

I'm sure you git gurus will immediately know what this all means and how to clean it up - I look forward to clarity.

Update:

Thanks to Kata for a thorough answer (very likely to be accepted). But I have a few followup questions that would be hard to read in a tiny comment (I wish comments could be longer and have more formatting), so bear with me a little longer. I guess they can be answered either by a comment or by Kata adding to his answer - whatever works.

Because it is not sure that in the past php7 was merged into master.

If it's not sure that php7 was merged, why is it listed in --merged? (The thought of git being "unsure" about anything worries me...) I have no idea how to move a branch head later, so that scenario is unlikely. However, there was a time when I did something locally that caused Github to refuse pushes (I think I might have amended files to an already-pushed commit) - someone else helped me look it over, and I ended up doing push -f (I'm the only developer). Perhaps that caused a pointer to get out of place...

Anyway, I know the php7 code is part of my current codebase, because it is happily running on PHP7 now. And I recognize several of the commits as having been done on that branch. In fact, it's possible that all the commits from the time I created php7 until I merged it (46 commits) were done on that branch (i.e. no flipping back to master to fix bugs in the meantime). Would that explain why there is no extra line in the DAG beside the master line?

If so, then perhaps texlabels is the only branch I ever interrupted to fix a bug on master, since it's the only extra line in the DAG. Also, no other branch has an entry for "Merge branch 'so-and-so'" - is it because without any master commits, merge doesn't need to change any content (just move pointers)?

Because dashboard was created locally and has never pushed to remote.

Oh, I didn't realize that a basic push doesn't include branch information. Apparently Github knows about php7 because I no doubt did pushes while it was checked out. (Is that right?)

I've also now learned about tags, and I created a tag at the point I deployed my project on a new server with PHP7 and other differences. I would be happy to not have the php7 or dashboard branches anymore, as long as my current codebase stays my current codebase. But if you're saying that there is some uncertainty about their merge status, is it safe to delete them? (Deleting things sounds scary, but if it's merely a pointer...)

Update #2:

After Kata mentioned that without --all I might not be seeing everything, I did a diff between with/without that flag. I discovered that at the very beginning of my php7 work, the first four commits were repeated, first on a "branchy-looking" branch (only seen in --all), and then on the main line with everything else. Here is the relevant snippet from git log --graph --decorate --all --oneline:

* 3b1c25a Removal of .html files (won't work with the new server config) and $client in db connect file
* dccc3f3 minor edits
* 20a1aab remove .idea files from repo
* 629d891 Second day of cleanup for PHP7: various things, hacking away at errors one at a time (note: I
* a669fa0 First day of cleanup for PHP7:   * mysql_ to mysqli_ (major functions, anyway)   * convert my
| * 51738d1 (refs/original/refs/heads/php7) minor edits
| * f1aa0f9 remove .idea files from repo
| * 38aef9b (refs/original/refs/remotes/origin/php7) Second day of cleanup for PHP7: various things, ha
| * cfcaa51 First day of cleanup for PHP7:   * mysql_ to mysqli_ (major functions, anyway)   * convert
|/
* 294ef21 Feature: batch category delete

I'm guessing that the weirdness was somehow caused by me accidentally using the command line on my VM to do two of the four ("remove .idea..." & "minor edits") instead of OsakaWebbie like the rest of them. (The user doesn't show in --oneline, but you can see that on Github.) But no matter how it happened, I'm assuming the first set (cfcaa51 thru 51738d1) will go away when I delete the branch, but since they are repeated (and I know my current codebase reflects the changes in those commits), it should be okay, right?


Solution

  • You're better to think branches as heads (or pointers). What git log shows you is a DAG of the commits and a list of heads pointing to particular commits. What you can see at the current moment is something like: the php7 head is pointing to this commit. You cannot know in the past how the php7 head was moving since git allows us to move heads in a free manner.

    Regarding git branch --merged [<commit>], look into its document, the command simply shows all the heads that can be reached from the <commit>. In your case, the <commit> is HEAD which means master. So the result is obvious: dashboard, master, and php7 are reachable from HEAD. Notice that by default git branch does not show remote heads, add -a option for that.

    Now I think your questions could be answered.

    Commits I remember doing on the php7 branch are all on the same orange line as master. If I merged and then deleted the branch (logical), why is it still listed as a branch at all?

    Because it is not sure that in the past php7 was merged into master. Even if it was, it could be always that at some point later the php7 head was moved, so we cannot see the vestige of the merge caused by php7.

    Why is there an extra "origin" label on the last of the php7 branch? I would think recent pushes would cause remote to look just like local, with only one "origin" label on the latest commit. Surely remote doesn't consider php7 to be not-yet-merged - that would be horrible, as that code is critical.

    That label simply means that currently there are 2 heads -- php7 and origin/php7 -- pointing to that commit.

    dashboard is not known by remote at all (https://github.com/OsakaWebbie/kizunadb) - how could that be?

    Because dashboard was created locally and has never been pushed to remote.

    I would be happy to delete it (I don't keep old branches for nostalgia), but git branch -d texlabels says: error: branch 'texlabels' not found. The most branchy-looking thing in the log isn't a branch? My head hurts.

    You know the existing of the texlabels head just because you see its name in the commit message. But actually that head was deleted in the past thus at the moment you cannot delete it again.

    Update:

    To answer some more concerns of @OsakaWebbie

    The --merged option is named based on the fact that, in normal development (i.e., you don't abnormally move heads, with git reset for example), if a head child can reach to a head parent, it means that parent was merged into child at some point in the past. In other words, child does inherit parent.

    ----------------------- child
             /
            /
         parent
    

    But I empathise with you about this, actually there are complaints about the naming of git commands and their options.

    Back to the story of your php7 head, I think the situation should be clear now.

    A merge does not necessarily cause two paths joining into one. Indeed, as you told, since you switched to php7 and appended 46 commits to it, you hadn't appended any commit to master, so the history before the merge looks like this:

    ----------------- master
                        \
                         ----------------- php7
    

    Then you switched back to master and made the merge: git merge php7. Because php7 is a child of master, the result of the merge should be exactly php7. Thus, git will simply move master forward and point it to the commit being pointed by php7. This kind of merge is called fast-forward. Then you have:

    -------------------
                        \
                         ----------------- php7, master
    

    which essentially is:

    -------------------------------------- php7, master
    

    But fast-forward did not happen with the merge of texlabels head. You created the head, switched to it, coded something and appended some commits. Then you switched back to master, also coded something and appended some commits. So you have:

    ----------------- master
         \
          ----------- texlabels
    

    As you see texlabels is not a child of master. Hence this cannot be a fast-forward, merging texlabels into master will cause:

    ----------------- (old master) -- master
         \                         /
          ----------- texlabels
    

    That's why it is the only branchy-looking thing in the history.

    Regarding your two last concerns:

    Oh, I didn't realize that a basic push doesn't include branch information. Apparently Github knows about php7 because I no doubt did pushes while it was checked out. (Is that right?)

    git push <remote> [<head>] only pushes the provided <head> (and its ancestor commits of course). If <head> is not provided, then the head that is being pointed by HEAD will be used -- it is php7 in your case. To push all the heads, use --all option.

    But if you're saying that there is some uncertainty about their merge status, is it safe to delete them? (Deleting things sounds scary, but if it's merely a pointer...)

    Yes, heads are merely pointers, but please be careful: after deleting a head, some commits may be abandoned -- i.e., they cannot be reached from any head -- and will be cleaned (deleted) by the GC of git. If you are sure about what you are deleting, go ahead.


    Some more notes:

    1. The push -f is not related here, it causes unexpected things in the remote (e.g., losing some commits), not in the local.
    2. By default git log shows only the commits that can be reached from HEAD, use --all option to show all commits.

    Update #2:

    About the 4 extra commits

    It's very likely that at the commit 51738d1 minor edits you issued a git filter-branch command to massively modify something about those 4 commits (modify author names -- I guess). Then you had 4 new commits created (from a669fa0 to 3b1c25a). The 4 old commits (from cfcaa51 to 51738d1) are still there because they are still reachable from the heads refs/original/.... These heads were created by the backing-up mechanism of git filter-branch.

    You are totally safe to delete those 4 extra commits. They are completely unrelated to the 4 "main" commits.