10

Imagine this:

git-diff-three-dots

I created the branch "B" three days ago. It is the branch I am currently working on. Now I want to know what changed since the branch was created (X).

This gives my a diff between B and X (like the dashed line in the picture):

git diff A...

Above command is handy, since it is short and I don't need to remember "X".

But: I am super lazy. I don't want to remember which branch my current branch was created from.

How can I avoid to remember/type A?

I am looking for a way which needs no input from my side. It should list all changes since the last time a branch was created.

guettli
  • 25,042
  • 81
  • 346
  • 663
  • Did you mean this works: `git diff X...`? Do you want to diff A or X? – Igal S. Jul 01 '21 at 09:13
  • @IgalS. I am looking for a diff which is like the dashed line in the picture. – guettli Jul 01 '21 at 10:10
  • 1
    @IgalS. No, `A...` was meant. Note the three dots. With `git diff` they mean "the common ancestor", i.e., `X`. `git diff A..` would compute the difference to `A`. Note the two dots. – j6t Jul 01 '21 at 11:29
  • @guettli : can you give a few extra details on how you work ? for example : do you have an identified set of branches (say : `master`, `develop`, and `release-{xx}`) that would be valid "fork points" for branches ? – LeGEC Jul 07 '21 at 07:43
  • @LeGEC In 90% of all cases, I create a feature branch from the master. In 10% I branch from a feature branch of a co-worker. There are release branches, but they don't matter in this context. – guettli Jul 07 '21 at 08:51
  • ok, so could your need be translated to : spot the most recent commit in current branch that falls in the history of `{master, other feature branches}` ? (note : you can adapt the second part of my answer, to only list `{master, other feature branches}` in the `--not` part) – LeGEC Jul 07 '21 at 09:23

6 Answers6

10

Well, @{-1} might be A. But then again it might not. It simply means "the branch I was on previously". Similarly, looking through the git reflog might tell allow you to deduce where you were when you created the current branch.

But there is nothing about the current branch itself that tells you any of that. The real problem here is that you have a different idea of what a "branch" is than Git does. The phrase "since the branch was created" is probably misleading you.

You seem to think that B is "everything since X, up to the end of B." It isn't. B is just one commit: the one labelled B in your diagram. Everything else backwards from that — the commit before B, and the commit before that, and X, and the commit before X, and the commit before that, backwards all the way to the root commit — have exactly the same status. They are commits reachable from B, and that is all they are.

So there is nothing special about X in Git's mind. You think it is special because it is where A and B "meet". But to distinguish that fact you must know the names of B and A. You are seeing a topology that depends upon A; you must communicate what you see to Git if you want Git to help you.

Once you are willing to talk about both A and B, then fine, you can ask for git diff ...A and git diff A... to find out what changed since X. Or you can talk about git merge-base A B to find X. But only a human being can distinguish that the key here is A.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Does a branch not have a birth-day (a commit where it started to exist)? – guettli Jul 01 '21 at 10:13
  • 2
    @guettli No. A "branch" is just a label for a commit, nothing more. You can delete the branch *B*, then attach the same label *B* to the same commit. When would you now think that branch *B* was born? – j6t Jul 01 '21 at 11:33
  • @j6t If a branch got deleted, the created again, then I think the branch was born again. The old life of the branch will be gone. No need to track it. But tracking the birth could make sense. – guettli Jul 01 '21 at 12:06
  • 2
    you are right, except for "you must know the names of B and A." One can examine the "nothing special" commit's place in the commit tree to find the closest ancestor commit that is also the ancestor commit of another commit. In other words, you don't have to know the name of `A`, but know an algorithm to find `A` the correctly reflects the semantics of what you mean by `A`. @VonC's answer does that as far as I can tell. – Inigo Jul 07 '21 at 23:32
  • @Inigo In above image there are commits in branch A after commit X. But there could be no commits on A, too. Then you can't discover X be looking on B backwards until you find a commit with two ancestors. Do you think git itself can be altered so that this information ("birthday of a branch") is available in future releases of git? – guettli Jul 08 '21 at 09:20
  • 1
    @guettli Good catch, but it is easily corrected: *Find the closest ancestor commit that is also the head of or ancestor to the head of another branch*. As to your question: My gut reaction (which may be wrong) is that doing so would have been obvious to Torvalds, and he didn't for a reason. What happens when you rebase a branch? What if the branch you branch off of was rebased from a point *before* your proposed "birthday"?, leaving your branch as a branch of an orphan sequence of commits. Your idea may be good, but it needs more analysis. Propose it to the Git maintainers. – Inigo Jul 08 '21 at 14:57
  • If it weren't for the bounty, we'd close this as a duplicate of https://stackoverflow.com/questions/3161204/how-to-find-the-nearest-parent-of-a-git-branch, with probably the best answer going to [Chef T-Rex (aka Velociraptor)](https://stackoverflow.com/a/3162929/8910547). /cc @chris-johnsen – Inigo Jul 10 '21 at 10:25
5

But: I am super lazy. I don't want to remember which branch my current branch was created from.

Then you need to script it, in an executable called git-diffca, which can then be called as git diffca (ca for "common ancestor")

In that script, you need to decide what criteria you want to chose amongst all the candidate branches:

     c--c--c     (C)
    /
a--Y--X--a--a    (A)
       \
        b--b--b  (B, HEAD)
     

What do you want to look at: changes from Y (a common ancestor between branch C and B)? or from X (between A and B)

I would look at all branches, compute git merge-base <aBranch> B for each one, and take the most recent commit (X in the case of the schema)

Then the script would display git diff A... (since git merge-base A B yielded the most recent common ancestor with B)

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • thank you very much. I am unsure if this is an answer. The question is now: How to implement the script `git-diffca`? – guettli Jul 11 '21 at 15:01
  • @guettli Exactly as I describe: there are multiple possible branches: the script choses the most recent base merge. – VonC Jul 11 '21 at 15:35
3

I don't want to remember which branch my current branch was created from. How can I avoid to remember/type A?

Tell Git to track it for you. To make this happen, when you want to branch off your current checkout, add the t option:

git checkout -tb B

will switch to a new branch B tracking your existing ... whatever branch, A, right? Notice I didn't have to remember or type it. See the git branch -u option for setting a new upstream after the fact.

When you've told git what branch you're tracking, git diff @{u}... or git diff @{upstream}... does exactly what you want. You could even make it an alias, git config --global alias.du diff @{u}....


Note that Git ordinarily sets up tracking on upstream branches from remotes automatically: if you say git checkout fix32943 and you don't have a fix32943 branch and there's exactly one remote with a fix32943 branch, Git will make a local fix32943 branch and set it up, as if you'd done the above because that's exactly what it's doing for you, to track that upstream. So git diff @{U}... often already works the way you want it to.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • Grrr, first I thought this is the perfect solution, but this breaks my usual flow: `git co -b foo`, code, commit, push. Then I see a message: `fatal: The current branch foo has no upstream branch.... use: git push --set-upstream origin foo`. I copy+paste the line, and then create a pull-request. If I use `git co -tb foo`, then this does not work any more. Sad, but true. – guettli Jul 11 '21 at 15:16
  • Check the `push.default` config, you might like what `current` or `matching` do. – jthill Jul 11 '21 at 15:39
2

I will repeat what @matt has amply detailed in his answer : git tracks commits, but not branches


There is however something that looks like "the life of that branch", it's the reflog for that branch :

git reflog my/branch

You can inspect the history and see if you find any information there.

For example : the commit when the branch was created would be :

git reflog my/branch | tail -1
  # and use something like `| awk '{ print $1 }'` for the sha,
  # or parse the 'branch: Created from ...' message (if there is one)
  # to see if there is a branch name

git reflog --format="%H" my/branch | tail -1   # if you want only the sha

But again, there are caveats : if you have run git rebase during the lifetime of the branch, then the initial commit is probably not the fork point you are looking for anymore.


Another proxy could be : look at the commits that are in the history of branch B, and in the history of no other branch :

# the following will list all branches except 'my/branch' :
git branch --format="%(refname:short)" | grep -v "my/branch"

# here is a way to list all commits on 'my/branch', excluding commits
# from all other branches.
# Adding `--boundary` will also display the first 'hidden' commit
git log --boundary --oneline --graph my/branch --not $(<the command above>)

# you can also replace 'git log' with 'git rev-list', if you only want the hashes

With some luck, the commit mentioned as a boundary is the one you are looking for.
The caveats being : if there are some merges in the history of your branch, you may have several "boundary" points, and if there is another branch that "forked off" from B more recently than the X you are looking for, then the boundary would be the fork point with that branch, and not with master or develop.


update based on your comment : you can

  • list all feature branches in your local clone (except your own branch)
  • look for the "fork point" of your current branch from any of those branches
# get the name of your current active branch :
mybranch=$(git rev-parse --abbrev-ref HEAD)

# make a separate function to list on stdout "all the branches except mine"
list_branches () {
  # list the `master` branch
  echo "master"

  # get all 'origin/somefeature' branches, excluding mybranch

  # suggestion: list remote branches (you will get all features,
  # even if you haven't created a local checkout of a branch)
  git branch -r --format="%(refname:short)" |\
    grep "/feat/" |\    # you can grep for a pattern if you have one
    grep -b "$mybranch" # remove your branch from the lot
}

# get the 'boundary' commits of your branch with respect to
# all the other branches
base=$(
  git rev-list --boundary --graph "$mybranch" --not $(list_branches "$mybranch") |\
    grep -e "^-" |\  # rev-list will prefix boundary commits with a '-'
    tail -1 |\       # if there are several, only take the last one
    tr -d '-'        # delete that leading '-'
)

# with some luck, we computed the correct base commit :
git diff $base...
LeGEC
  • 46,477
  • 5
  • 57
  • 104
0

As everyone before me already said, git tracks commits and not branches. You could tag a commit before you branch from it to make it easier, but that still requires you to manually do the tagging.

Steps:

  • git tag <some descriptive tag>
  • git push origin <tag name> if single tag
    • git push --tags for multiple tags

then from here you can easily look at logs and see changes as needed.

Or you could just name your branch branchName_branchedFrom type of naming convention and then it'll be easier to track (where branchedFrom could be the short-sha1 or if you've tagged it, could be the tag name.

codeLvr
  • 3
  • 4
0

It's not possible after the fact (and doing git diff A... is the best and shortest option). However, if you know beforehand that one branch is upstream of another, you can set up the "upstream configuration" when creating the branch with the -t/--track option:

git checkout --track --branch newbranch upstreambranch

To configure the upstream for an existing branch, use the git branch command with the -u/--set-upstream-to= option:

git branch --set-upstream-to=upstreambranch existingbranch

(in your example: newbranch/existingbranch == C, upstreambranch = A)

You can then access the current branch tip commit of the upstream branch with newbranch@{upstream} or newbranch@{u} for short. To get the upstream of the current branch (HEAD), @{u} suffices.

Your command then becomes:

git diff newbranch@{u}...newbranch

or, with newbranch checked out:

git diff @{u}...
knittl
  • 246,190
  • 53
  • 318
  • 364