0

extension to this question . My goal is to do some cleanup by deleting branches that are already merged to current one.

So I start with

git branch --merged

However, this will also pick up branches that are created from current one but has no commits on them. Is there a way to limit the results to branches that has at least one commit in them?

Example :

git checkout develop
git checkout -b newFeature
touch a
git add . && git commit -a -m "sample"
git checkout develop
git merge newFeature
git branch branchIwantToKeepForFuture
git branch --merged  --> this shows both newFeature and branchIwantToKeepForFuture

I need to find a way to show only newFeature but not branchIwantToKeepForFuture so that I can run the delete command with those args

Arun K
  • 25
  • 1
  • 7
  • The easy way to handle this is to just create the "for future" name *after* deleting the unwanted names. (Or, re-create it after accidentally deleting it.) – torek Dec 05 '19 at 23:27
  • You could do a script and compare revisions referred by the branches (see `git rev-parse`), also not sure what do you want to see if the develop branch is ahead. In any case it's quite weird requirement, not sure if it has any sense. – kan Dec 05 '19 at 23:36
  • if the develop moves ahead, I still want it to show newFeature and no branchIwantToKeepForFuture . We have a large team that is constantly creating branches and I dont want to delete a branch that someone just created and drive them crazy when they try to push it . Though I gave these examples in local, I would be adding the -r to find remote branches – Arun K Dec 05 '19 at 23:39
  • Deleting a remote-tracking name locally doesn't do you much good: a `git fetch` just creates it again. You must delete the name that the remote-tracking name is tracking, which means going to your upstream repository (e.g., `origin`) and deleting the name *there*. – torek Dec 05 '19 at 23:49
  • Meanwhile, they might have done a merge from their `newFeature` and pushed it but might still be developing on their `newFeature`. Have them clean up their own branches when they're done with them. Use `git fetch --prune`, or set `fetch.prune`, so that your local Git will delete remote-tracking names that no longer have a branch on `origin` to be tracking. – torek Dec 05 '19 at 23:51

1 Answers1

0

I put the short answer in a comment (just create the branches again, or later) but there's a fundamental error in the question:

... this will also pick up branches that are created from current one but has no commits on them.

Those branches do contain commits. They just contain the same commits as your current branch.

Remember, a branch name is simply a name—whatever string you like, with some minor constraints like "no consecutive dots" and "no at signs" and so on; see the git check-ref-format documentation)—that contains one hash ID. That one hash ID is the commit that is the tip of the branch. This is the definition of a branch name in Git: it is

  • a name
  • containing a hash ID
  • of a commit

and that commit, by virtue of having that name that identifies it, is the tip of that branch.

If more than one name hold the same hash ID, that one commit is the tip of multiple branches, all at the same time.

Meanwhile every commit holds a list of hash IDs—usually just one entry long—which are the parent commits of that one commit. These hash IDs are part of the commit itself. The hash IDs are, in a sense, the "true names" of each commit. If we draw the commits, replacing actual hash IDs with single uppercase letters, we get a picture like this:

... <-F <-G <-H   <--master

The name master holds the hash ID of commit H. Commit H holds the hash ID of earlier commit G. G holds the hash ID of earlier commit F, which holds another hash ID, and so on. So the name master points to H, and H points to G which points to F and so on.

If we have multiple names that point to H, then all those commits—up through H—are on all those branches:

...--F--G--H   <-- master, develop
            \
             I--J   <-- feature/short

Meanwhile the branch name feature/short points to commit J, which points back to I, which points to H, which points to G and so on. So commits through H are on all three branches, while commits I and J are—currently—only on feature/short.

Git will move a branch name. If we make a new name temp that points to H we get:

...--F--G--H   <-- master, develop, temp
            \
             I--J   <-- feature/short

If we now git checkout temp and then make a new commit, the new commit gets a new hash ID—but we'll just call it K here—and the name temp moves to point to the new commit:

             K   <-- temp
            /
...--F--G--H   <-- master, develop
            \
             I--J   <-- feature/short

To create a branch, we pick any existing commit and make a new name that points to that existing commit. To delete a branch, we simply erase the name. Let's delete temp (we'll have to git checkout something else first):

             K
            /
...--F--G--H   <-- master, develop
            \
             I--J   <-- feature/short

Commit K still exists. You just can't find it now. Git will eventually reclaim it once there is really, truly no way to find it—but Git generally will hang on to some hidden ways to find K, in case you want it back, typically for at least a month or so. As long as those hidden ways work to find K, commit K will stick around. But with no obvious way to find it, we'll just stop drawing it entirely.

Now let's move the name develop to point to commit J, perhaps by doing:

git checkout develop && git merge --ff-only feature/short

The picture now looks like this:

...--F--G--H   <-- master
            \
             I--J   <-- feature/short, develop (HEAD)

The (HEAD) annotation is just to remember which branch we have checked-out right now.

What git branch --merged does is look for names where:

  • the given branch name points to a commit (which is always), and
  • the commit to which that name points is reachable from your current HEAD

So if you're on develop, and commit K does not exist, the names that point to commits that you can get to by starting at J and working backwards are:

  • master, because H is reachable by starting at J and going back two steps, and
  • feature/short, because J is reachable by starting at J and doing nothing at all.

Should feature/short move forward one step like this:

...--F--G--H   <-- master
            \
             I--J   <-- develop (HEAD)
                 \
                  L   <-- feature/short

then git branch --merged will stop listing feature/short, because we cannot reach commit L by starting at J and working backwards. But adding another commit to develop has no effect on git branch --merged output:

...--F--G--H   <-- master
            \
             I--J--M   <-- develop (HEAD)
                 \
                  L   <-- feature/short

as we can start at M, count back three steps to J, I, and then H, and we have arrived at the commit to which master points, so git branch --merged still lists master.

You can, if you wish, get your list of branches where:

  • the name points to a commit that is reachable from HEAD and
  • the name points to a commit that is not the commit that HEAD identifies

but this will take multiple steps: first, get the list of reachables via git branch --merged, then compare the result of:

git rev-parse HEAD

(which is the actual hash ID of the current commit) to the result of git rev-parse on each branch name (which is the tip commit of each of those branches).

Beware of using this on remote-tracking names like origin/master

Note that all of these results are only valid for your names. If you apply them to remote-tracking names, they tell you about those remote-tracking names as they exist right now in your repository. If the branch names in some other Git are highly active, you'll need to run git fetch to update your remote-tracking names first. That way your origin/master, origin/feature, and so on will be in sync with the other Git.

If that other Git is extremely active, however, in the time it takes you to run git branch -r --merged and do some more checking, your own remote-tracking names may become out of date. You'll need to run git fetch again to update them, and try again. By the time you get a result, they may be out of date—and so on. Unless you control the other repository, and can make people stop updating it, you're racing against them to get an accurate picture here. This is generally unwise.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks for the explanation. I understand that the question was fundamentally wrong. Accepting this as the answer. – Arun K Dec 17 '19 at 20:06