0

I've created a test alias and in this alias, i want to be able to log only the current branch i'm on (bear in mind this is a simplified version of the log, the final one has way more details, colours and so on).

My alias looks like this:

test = "!git log \"$(git rev-parse --abbrev-ref HEAD)\""

but when I type git test i get the 2 commits on the branch plus all the previous commits from different branches (which I don't want).

Any idea? Thanks

Nick
  • 13,493
  • 8
  • 51
  • 98

1 Answers1

1

Your alias as shown here is not faulty, it's your expectation (your idea about what Git means by "on a branch") that's wrong: that is, what you mean by "on a branch", and what Git means by "on a branch", simply disagree.

The basic problem is that Git branches don't mean what people expect them to mean. (One could argue that this means Git is wrong. Some other VCSes such as Mercurial define branches the way you'd like Git to define them.) In Git, a branch name points only to one commit, which Git calls the tip commit of that branch.

Each commit, however, points back to some number (usually one) of previous or parent commits. Git uses this to string the commits together for many purposes, including showing them with git log. We can draw this by, on paper or whiteboard or in a StackOverflow answer, drawing the branch name with an arrow coming out of it, pointing to the last commit that is contained within the branch, and then having that commit—whose "true name" is one of those big ugly commit hash IDs, but we'll use single letters here—have an arrow out of it, pointing back to its parent, and so on:

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

What git log does is start with the commit to which the branch name points, and show it. Then it moves to the commit's parent, and shows that. Then it moves further back in time (leftward) and shows that commit, and so on.1

All Git internal links are always backwards, so as long as we put newer commits towards the right, we don't need to draw the internal arrows. We just know they go backwards. So let's draw a more complex setup:

...--F--G--H   <-- master
            \
             I--J   <-- feature

The name feature points to commit J, so commit J is on the feature branch. Commit J points back to I, which points back to H, which points back to G, and so on. So every commit is on branch feature, even though commit H is also the tip commit of branch master. Commits up through H are all on master, so commits through H are on both branches.

What you'll want to do here, rather than logging all commits contained within your current branch—for which a simple git log suffices—is to ask Git to log commits that are contained within your current branch but not contained in some other branch(es). The tricky part lies in figuring out how to exclude those commits.

One way is to list every branch name, then strip away the current branch name, and then tell git log: List for me every commit contained within the current branch, starting from HEAD, but stop listing upon reaching any commit contained within any other branch. To do that, you could get the current branch name (as you do now, or in some other way) and exclude that from a list of all branches (that you would get somehow):

git log HEAD --not br1 br2 br3 br4

(if the other four branches are named br1 through br4).

This typically isn't necessary, though. In general, whenever you are working on some branch, you will designate some other name—sometimes another branch name, sometimes a remote-tracking name like origin/master—as the current branch's upstream. Each branch can have just one upstream setting (or no setting at all), and once you have an upstream set, you can use the symbolic notation @{upstream} or the short version @{u} to refer to it:

git log HEAD --not @{u}

There's a shorter syntax for this, because it's so often useful: prefixing a commit specifier with a hat ^ character means "not", so you can write:

git log HEAD ^@{u}

and then there's an even shorter syntax, because this is so common:

git log @{u}..HEAD

literally means all commits reachable from the current HEAD commit, except for all commits reachable from the current branch's upstream.

So what you probably want is just the @{u}..HEAD notation. That's not the same as explicitly excluding all other branches. Suppose, for instance, we have a graph that looks in part like this:

       M   <-- origin/br2
      /
...--H   <-- origin/master, origin/br1
      \
       I--J   <-- br1
           \
            K--L   <-- br2 (HEAD)

Suppose further that the upstream for br2 is origin/br2. Asking for the commits in origin/br2..br2 (i.e., @{u}..HEAD) means you will select commits L, K, J, and I. Asking for the commits that are contained in br2 but no other branch (i.e., not br1) will get only L and K. If that—getting just L and K—is what you want, then you do in fact need to enumerate all the branches.

(The trick to enumerate all branches is to use git for-each-ref with the refs/heads/ prefix. You should consider using git symbolic-ref to read the current branch name, as well, if you're going to write a lot of scripts or fancy aliases. These commands, git for-each-ref and git symbolic-ref, are what Git calls plumbing commands, meant for writing scripts and complex aliases. They behave in a much more strictly-defined, aimed-at-computer-use manner, vs "user oriented" commands like git log or git diff. Calling those "user oriented" is a little bit laughable but they are clearly more user oriented than, e.g., git diff-tree.)


1This is simplified a bit. In the case of looking at more than one branch name, or following a merge commit to multiple parents, git log must linearize the graph topology somehow. It does so through a priority queue, showing the "highest priority" commit next and adding that commit's parents to the queue. For simple linear chains, though, the idea that Git moves backwards through history suffices.

torek
  • 448,244
  • 59
  • 642
  • 775