3

I have a local git repository with a couple of branches. I want to locally delete all non-master branches, as well as the commits that are not on the master branch. How do I delete all commits that are not on the master branch?

I am later doing a cherry-pick that fails because it reaches a commit that was a merge, so it has two parents. What do I need to do so that it only has one parent (from the master branch)?

Software Dev
  • 910
  • 3
  • 10
  • 27

2 Answers2

4
git branch | grep -v "master$" | xargs git branch -D 

The above will get all branches, filter out those that include "master", and then delete them.

When Git next does garbage collection, the now-orphaned commits will be deleted. If you want to force that to happen now, you can run:

git gc --prune=now --aggressive
John Brodie
  • 5,909
  • 1
  • 19
  • 29
  • I am later doing a cherry-pick that fails because it reaches a commit that was a merge, so it has two parents. What do I need to do so that it only has one parent (from the master branch)? – Software Dev Aug 05 '19 at 16:42
  • 'git branch' is porcelain its output shouldn't be relied upon. It is better to use a low level command such as 'for-each-ref' ```git for-each-ref --format='delete %(refname)' refs/heads | grep -v 'refs/heads/master' | git update-ref --stdin``` – stellarhopper Aug 05 '19 at 17:44
  • Can you explain this long command? Will it delete all the diffs not on the master branch? – Software Dev Aug 05 '19 at 18:30
1

The part of your question about deleting all non-master branches is already answered. However:

I am later doing a cherry-pick that fails because it reaches a commit that was a merge, so it has two parents. What do I need to do so that it only has one parent (from the master branch)?

You can't do anything about this. No existing commit can ever be changed: not by you, nor by Git. If you're deleting branch names with the idea that this might also change existing commits, it won't: all it will do is make it impossible to find some existing commits.

Once a commit is un-find-able (unreachable, in technical terms), git gc will (eventually) delete it entirely. But a merge commit, with its two parents, makes both parents find-able: finding the merge—by starting at some branch name and working backwards through history—inherently finds both parents, and then the parents' parents, and so on.

For (much) more about this, see Think Like (a) Git.

What you probably want to do, rather than trying to modify this existing commit, is one of these two things:

  • Don't cherry-pick the merge at all. Cherry-pick, instead, all of the commits behind the merge (on both legs / sides of the merge).

  • Or, do cherry-pick the merge, but use git cherry-pick -m number to specify which parent will produce the diffs for the cherry-pick.

You can think of git cherry-pick as meaning: Take the snapshot (commit) I name, and turn it into changes (diffs against its parent), then apply those same diffs to my current commit. That's just an approximation though.

You might have a series of commits that can be drawn like this:

          o--o--A--B--o--...--o   <-- branch1
         /
...--o--*
         \
          o--o--o--C   <-- branch2 (HEAD)

Here, your current commit is commit C (C stands in for its actual hash) and you're on branch branch1. If you git cherry-pick B, Git compares A vs B and merges whatever happened there with the difference between A and C. Cherry-pick is therefore actually a merge, not a diff-and-apply; this is what leads to weird merge conflicts sometimes.

In any case, this obviously only works for non-merge commits, because a merge has two parents:

       o--...--A
      /         \
...--*           M--...
      \         /
       o--...--B

If you ask to cherry-pick M, should Git compare M to A, or to B? Which of A and B gets compared to your current commit?

The cherry-pick command's -m switch allows you to choose. If you choose A, Git will merge the A-to-M changes with the A-to-HEAD changes. The effect is to take all the changes that the merge introduced because of commit B! So this means you no longer need to cherry-pick some commits behind B. You got all of "their" (B's) changes relative to the merge base commit *—or rather, all that whoever did the merge into A thought were appropriate to keep.

Note that you still might want commits behind A, including commit * itself and things earlier. Or you may not, depending on what, exactly, you're doing. Git mostly provides tools rather than solutions; it's up to you to make use of these tools, however you like.

torek
  • 448,244
  • 59
  • 642
  • 775