L. Heinrichs
L. Heinrichs

Reputation: 161

display only the ahead-commits on git-branch that is ahead+behind compared to another branch

I recently inherited a git(hub) repository with

I managed to recreate the latest version of the overall git repo and created a new branch that has all the relevant commits, in a flat line (rather than having a knot of commits and branches). Lets call this branch Mr.Branch.

Mr.Branch runs through all (unit) tests and compiles well in both cmake for building the core and an embedded IDE for building the application that uses the core. Correct functioning was also tested on the target embedded device.

The old/previous branches still exist. Before removing them, I want to make sure everything necessary from these branches is actually part of Mr.Branch.

Maybe documentation content was updated or new tests have been added. For now, there is a chance for me to miss these changes. However, compared to Mr.Branch, the other branches are often something like 200 commits behind, 3 commits ahead.

Is there an option to find out what commits a branch is ahead of Mr.Branch and ignore the behind-commits somehow?

if possible, i would like to not use rebase, as there are a lot of conflicts and i would have to resolve the same conflict multiple times (as the branches are merged into each other)

Upvotes: 1

Views: 395

Answers (1)

torek
torek

Reputation: 488233

TL;DR

Use the two-dot notation, A..B or B..A. In your case this would be git log Mr.Branch..somebranch.

Long

Here are two ways to view some commit graph fragments and two labels A and B:

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

In this case, the ... sections may have as many commits as you like in them but do not cross-connect between them, so that * is the first shared commit.

Or:

          o--...--*--...--o   <-- A
         /        :
...--o--o         :
         \        :
          o--...--*--...--o   <-- B

where there are some kinds of cross-connections so that both *s are on both branches, after which there are more commits.

In both cases, older commits are to the left; newer commits are to the right. Commits link backwards—Git starts at some label, then works leftwards—so commits after the starred commit or commits are only reachable from one of the two labels.

If you run git log A or git log B, Git will start from the labeled commit (A or B respectively) and work leftwards. As soon as it reaches a commit that's on both branches, you see the commits from the other branch as well. In the simple case like the one at the top, it's pretty obvious why both commits are on both branches: that's where the development forked, so as we go backwards in time, that's where the development rejoins.

In tangled merge cases, it can be much less obvious why these commits are on both branches. The most common variety of this problem occurs with the "criss-cross merge":

       A1---M1--...--An   <-- A
      /  \ /
...--o    X
      \  / \
       B1---M2--...--Bn   <-- B

Here, someone merged commit B1 into branch A to produce merge M1, and someone—probably someone else since that makes it more likely—merged commit A1 into branch B to make merge M2. Commit A1 is on branch B via the path that starts at Bn, walks back through Bn-1, Bn-2, etc, to M2, and then goes to M2's A1 parent. This happens because when Git hits a merge, it goes to both parents, and the two parents of M2 are A1 and B1.

Similarly, commit B1 is on branch A through merge M1. Still, there are commits on both A and B that aren't on the other branch.

When you ask Git to compare A and B in some way, some commands will tell you how many commits are "on" (reachable from) the name A but not "on" (reachable from) the name B as the ahead count for A. For instance, using the simple case:

       A1--A2--A3--A4   <-- A
      /
...--*
      \
       B1--B2   <-- B

there are four commits "on A" that are not "on B": these are all the ones along the top line, A1 through A4.

At the same time, there are two commits "on B" that are not also "on A": these are all the commits along the bottom line. (There are probably very many commits on both, along the middle line.) If the command gives a "behind" count, this is where that came from.

The two-dot notation

When you use the two-dot notation, such as B..A, you tell Git: Find commits reachable from the right-side name A that are not also reachable from the left-side name B. Of course, B2 and B1 are reachable from B, but that doesn't matter: the point is that * and everything before it is also reachable from B. So this excludes the commits from * on backwards, while leaving the top-line commits from A: A4, A3, A2, and A1.

The three-dot notation

Note that Git also has a three-dot notation, e.g., A...B. This tells Git to look at commits that are "on" (reachable from) either branch name, excluding commits that are "on" both branches. So given the last diagram above, this would include six commits: A1 through A4 plus B1 and B2. Keep in mind that Git generally works backwards though—from right to left, as it were—so you'll generally see these in the other order (they may be intermixed as Git also does some by-commit-date sorting).

Getting both counts simultaneously

From the command line, to retrieve one count or the other:

git rev-list --count B..A

and:

git rev-list --count A..B

will tell you, separately, how far A is "ahead of" B (by counting A4, A3, A2, A1 = 4 for instance) and then how far B is "ahead of" A (by counting B2, B1 = 2). You can get both counts at once using:

git rev-list --count --left-right A...B

which will give you "4 2" as its output: 4 on A that aren't on B, 2 on B that aren't on A. This means that A is 4 ahead of, and 2 behind, B—or equivalently, B is 2 ahead of, and 4 behind, A.

The above applies to many Git commands, but not to git diff

The git log command, which lets you view commits, uses these two- and three-dot syntax formats to let you view the commits you want.

The git rev-list command I showed above—which is Git's internal work-horse for listing out commit hash IDs; many other Git commands actually run git rev-list internally—use these in the same way. This means that, for instance, you can give git cherry-pick the A..B style commit range list to copy multiple commits from one branch to another. Watch out for the fact that in A..B, commit B gets included, but commit A gets excluded.

The git diff command, though, is special. It treats A..B as if you just wrote A B, and it treats A...B even more specially. Specifically, with A...B, Git tries to find the commit I kept marking as * above. If there's exactly one such commit, git diff will diff that commit against the B commit. Usually there will be one such commit and this will do what you want, once you understand all of this. I finally got a small bug fix in to Git to fix the behavior of git diff when there is no * commit, or when there are two or more; this should be in the next Git release.

Upvotes: 2

Related Questions