262

I want to list all commits that are only part of a specific branch.

With the following, it lists all the commits from the branch, but also from the parent (master)

git log mybranch

The other option I found, was to exclude the commits reachable by master and gives me what I want, BUT I would like to avoid the need of knowing the other branches names.

git log mybranch --not master

I was trying to use git for-each-ref, but it is also listing mybranch so actually it is excluding all:

git log mybranch --not $(git for-each-ref --format '^%(refname:short)' refs/heads/)

Update:

I'm testing a new option that I found a while ago, and till now seems that this could be what I was looking for:

git log --walk-reflogs mybranch

Update (2013-02-13T15:08):

The --walk-reflogs option is good, but I checked that there is an expiration for reflogs (default 90 days, gc.reflogExpire).

I think I found the answer I was looking for:

git log mybranch --not $(git for-each-ref --format='%(refname)' refs/heads/ | grep -v "refs/heads/mybranch")

I'm just removing the current branch from list of branches available and using that list to be excluded from the log. This way I only get the commits that are only reached by mybranch.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
dimirc
  • 5,297
  • 2
  • 20
  • 32
  • possible duplicate with http://stackoverflow.com/questions/53569 – StarPinkER Feb 13 '13 at 07:24
  • Already saw that question also, but not the same – dimirc Feb 13 '13 at 07:31
  • 2
    Are you sure that's what you want? I find it better to have a target in mind: "what's on my branch that's not in the upstream", or "what's on my branch that's not in master". Plus, while git is fast at pruning, this is going to get more expensive as you have more branches. I have a script I use which I call "git missing" after bzr's "missing" command. You can find it here: https://github.com/jszakmeister/etc/blob/master/git-addons/git-missing. – John Szakmeister Feb 13 '13 at 07:38
  • 1
    I actually need this for a post-receive hook, so "master" wont be always the branch to exclude – dimirc Feb 13 '13 at 07:43
  • Yes, the duplicate mentioned by StarPinkER worked well for me:git log $(git merge-base HEAD branch)..branch – charo Jul 23 '14 at 13:59

14 Answers14

203

From what it sounds like you should be using cherry:

git cherry -v develop mybranch

This would show all of the commits which are contained within mybranch, but NOT in develop. If you leave off the last option (mybranch), it will compare the current branch instead.

As VonC pointed out, you are ALWAYS comparing your branch to another branch, so know your branches and then choose which one to compare to.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Smilie
  • 2,039
  • 1
  • 10
  • 3
  • 5
    This is exactly what I needed, but it seems like there should be a more intuitive command (yes even in the world of Git) for this. – Seth Sep 10 '15 at 16:51
  • 13
    You can also do `git cherry -v master` to compare your current branch with the master branch. – Pithikos Jun 24 '16 at 14:13
  • 7
    The danger in using git cherry, is that commits are only matched if their file diffs are identical between branches. If any sort of merging was done which would make the diffs different between one branch, and the other, then git cherry sees them as different commits. – Ben Sep 15 '16 at 11:06
  • This just gives me `fatal: Unknown commit mybranch`. – Matt Arnold Sep 18 '19 at 12:08
  • 2
    @MattArnold you have to change the text "develop" and "mybranch" to branches which exist on your repo – Smilie Sep 19 '19 at 23:52
  • I don't understand why this get upvoted when OP explicitly said he wants to avoid to name another branch explicitly. And `git cherry -v develop mybranch` is very similar to `git log mybranch --not develop` that OP mentions. – fuujuhi Jan 08 '21 at 14:18
44

BUT I would like to avoid the need of knowing the other branches names.

I don't think this is possible: a branch in Git is always based on another one or at least on another commit, as explained in "git diff doesn't show enough":

enter image description here

You need a reference point for your log to show the right commits.

As mentioned in "GIT - Where did I branch from?":

branches are simply pointers to certain commits in a DAG

So even if git log master..mybranch is one answer, it would still show too many commits, if mybranch is based on myotherbranch, itself based on master.

In order to find that reference (the origin of your branch), you can only parse commits and see in which branch they are, as seen in:

Community
  • 1
  • 1
VonC
  • 1,042,979
  • 435
  • 3,649
  • 4,283
33

I finally found the way to do what the OP wanted. It's as simple as:

git log --graph [branchname]

The command will display all commits that are reachable from the provided branch in the format of graph. But, you can easily filter all commits on that branch by looking at the commits graph whose * is the first character in the commit line.

For example, let's look at the excerpt of git log --graph master on cakephp GitHub repo below:

D:\Web Folder\cakephp>git log --graph master
*   commit 8314c2ff833280bbc7102cb6d4fcf62240cd3ac4
|\  Merge: c3f45e8 0459a35
| | Author: José Lorenzo Rodríguez <lorenzo@users.noreply.github.com>
| | Date:   Tue Aug 30 08:01:59 2016 +0200
| |
| |     Merge pull request #9367 from cakephp/fewer-allocations
| |
| |     Do fewer allocations for simple default values.
| |
| * commit 0459a35689fec80bd8dca41e31d244a126d9e15e
| | Author: Mark Story <mark@mark-story.com>
| | Date:   Mon Aug 29 22:21:16 2016 -0400
| |
| |     The action should only be defaulted when there are no patterns
| |
| |     Only default the action name when there is no default & no pattern
| |     defined.
| |
| * commit 80c123b9dbd1c1b3301ec1270adc6c07824aeb5c
| | Author: Mark Story <mark@mark-story.com>
| | Date:   Sun Aug 28 22:35:20 2016 -0400
| |
| |     Do fewer allocations for simple default values.
| |
| |     Don't allocate arrays when we are only assigning a single array key
| |     value.
| |
* |   commit c3f45e811e4b49fe27624b57c3eb8f4721a4323b
|\ \  Merge: 10e5734 43178fd
| |/  Author: Mark Story <mark@mark-story.com>
|/|   Date:   Mon Aug 29 22:15:30 2016 -0400
| |
| |       Merge pull request #9322 from cakephp/add-email-assertions
| |
| |       Add email assertions trait
| |
| * commit 43178fd55d7ef9a42706279fa275bb783063cf34
| | Author: Jad Bitar <jadbitar@mac.com>
| | Date:   Mon Aug 29 17:43:29 2016 -0400
| |
| |     Fix `@since` in new files docblocks
| |

As you can see, only commits 8314c2ff833280bbc7102cb6d4fcf62240cd3ac4 and c3f45e811e4b49fe27624b57c3eb8f4721a4323b have the * being the first character in the commit lines. Those commits are from the master branch while the other four are from some other branches.

Lukman
  • 16,877
  • 5
  • 51
  • 60
13

The following shell command should do what you want:

git log --all --not $(git rev-list --no-walk --exclude=refs/heads/mybranch --all)

Caveats

If you have mybranch checked out, the above command won't work. That's because the commits on mybranch are also reachable by HEAD, so Git doesn't consider the commits to be unique to mybranch. To get it to work when mybranch is checked out, you must also add an exclude for HEAD:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=HEAD \
    --all)

However, you should not exclude HEAD unless the mybranch is checked out, otherwise you risk showing commits that are not exclusive to mybranch.

Similarly, if you have a remote branch named origin/mybranch that corresponds to the local mybranch branch, you'll have to exclude it:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=refs/remotes/origin/mybranch \
    --all)

And if the remote branch is the default branch for the remote repository (usually only true for origin/master), you'll have to exclude origin/HEAD as well:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=refs/remotes/origin/mybranch \
    --exclude=refs/remotes/origin/HEAD \
    --all)

If you have the branch checked out, and there's a remote branch, and the remote branch is the default for the remote repository, then you end up excluding a lot:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=HEAD
    --exclude=refs/remotes/origin/mybranch \
    --exclude=refs/remotes/origin/HEAD \
    --all)

Explanation

The git rev-list command is a low-level (plumbing) command that walks the given revisions and dumps the SHA1 identifiers encountered. Think of it as equivalent to git log except it only shows the SHA1—no log message, no author name, no timestamp, none of that "fancy" stuff.

The --no-walk option, as the name implies, prevents git rev-list from walking the ancestry chain. So if you type git rev-list --no-walk mybranch it will only print one SHA1 identifier: the identifier of the tip commit of the mybranch branch.

The --exclude=refs/heads/mybranch --all arguments tell git rev-list to start from each reference except for refs/heads/mybranch.

So, when you run git rev-list --no-walk --exclude=refs/heads/mybranch --all, Git prints the SHA1 identifier of the tip commit of each ref except for refs/heads/mybranch. These commits and their ancestors are the commits you are not interested in—these are the commits you do not want to see.

The other commits are the ones you want to see, so we collect the output of git rev-list --no-walk --exclude=refs/heads/mybranch --all and tell Git to show everything but those commits and their ancestors.

The --no-walk argument is necessary for large repositories (and is an optimization for small repositories): Without it, Git would have to print, and the shell would have to collect (and store in memory) many more commit identifiers than necessary. With a large repository, the number of collected commits could easily exceed the shell's command-line argument limit.

Git bug?

I would have expected the following to work:

git log --all --not --exclude=refs/heads/mybranch --all

but it does not. I'm guessing this is a bug in Git, but maybe it's intentional.

Richard Hansen
  • 44,218
  • 20
  • 84
  • 95
  • 5
    I came here wanting to know how to do Mercurial's `hg log -b `. I don't understand why people say git is unfriendly. /s – weberc2 Mar 08 '16 at 15:58
  • I don't know why `git log --all --not --exclude=refs/heads/mybranch --all` doesn't work but `git log refs/heads/mybranch --not --exclude=refs/heads/mybranch --all` does, with the same caveats about excluding HEAD and origin. – Ben C Sep 29 '17 at 13:14
  • This is the best and only real answer to all similar "this-branch-only" questions on SO. The issue about excluding `HEAD` or not can be fixed if one is ok to ignore tags, using `git log --branches --remotes --not $(git rev-list --no-walk --exclude=mybranch --branches --remotes)`. Note the different ref syntax for `exclude` here. – fuujuhi Jan 08 '21 at 14:13
  • ... Or using @BenC solution: `git log mybranch --not --exclude=mybranch --branches --remotes`, which is quite compact. – fuujuhi Jan 08 '21 at 14:27
11

Fast answer:

git log $(git merge-base master b2)..HEAD

Let's say:

  1. That you have a master branch

  2. Do a few commits

  3. You created a branch named b2

  4. Do git log -n1; the commit Id is the merge base between b2 and master

  5. Do a few commits in b2

  6. git log will show your log history of b2 and master

  7. Use commit range, if you aren't familiar with the concept, I invite you to google it or stack overflow-it,

    For your actual context, you can do for example

    git log commitID_FOO..comitID_BAR
    

    The ".." is the range operator for the log command.

    That mean, in a simple form, give me all logs more recent than commitID_FOO...

  8. Look at point #4, the merge base

    So: git log COMMITID_mergeBASE..HEAD will show you the difference

  9. Git can retrieve the merge base for you like this

    git merge-base b2 master
    
  10. Finally you can do:

    git log $(git merge-base master b2)..HEAD
    
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Frederic Nault
  • 834
  • 8
  • 12
9

I'm using the following commands:

git shortlog --no-merges --graph --abbrev-commit master..<mybranch>

or

git log --no-merges --graph --oneline --decorate master..<mybranch>
Alex
  • 378
  • 8
  • 18
  • +1. `--graph` only adds `*` as first character, so this option can be avoided. `--abbrev-comit` and `--decorate` seems to make no difference (for my branches). – Mayra Delgado Nov 04 '20 at 09:52
4

You could try something like this:

#!/bin/bash

all_but()
{
    target="$(git rev-parse $1)"
    echo "$target --not"
    git for-each-ref --shell --format="ref=%(refname)" refs/heads | \
    while read entry
    do
        eval "$entry"

        test "$ref" != "$target" && echo "$ref"
    done
}

git log $(all_but $1)

Or, borrowing from the recipe in the Git User's Manual:

#!/bin/bash
git log $1 --not $( git show-ref --heads | cut -d' ' -f2 | grep -v "^$1" )
John Szakmeister
  • 38,342
  • 9
  • 78
  • 72
  • +1. I was writing in my answer that you need to parse commits in order to find the origin of your branch, and you seem to have done just that. – VonC Feb 13 '13 at 08:10
  • Have you used log --walk-reflogs? I'm reading about this option and is is giving me the results i need till now, (still testing) – dimirc Feb 13 '13 at 08:46
  • @dimirc Reflogs are a different beast all together. It records branch points over time, generally for the purpose of recovery. I'm not sure what you're doing in your post-receive hook but maybe if you explained it, folks could provide a more sensible answer to your problem. – John Szakmeister Feb 13 '13 at 09:05
  • I just need to parse/check all the commit messages at a push. The case I want to cover is if someone pushes multiple commits at master and creates a new branch with multiple commits too. The post-receive hook need to check the changes done for both refs/branches, for example master and newbranch, I need to know the bounderies for the new branch to avoid parsing commit messages twice, since could be commit from master. – dimirc Feb 13 '13 at 09:18
  • 1
    Okay. Then I think the above approach I outlined really is what you want. With reflog, entries are going to fall off after some period of time, and I think that will cause you some headaches. You might want to look at using `git show-ref --tags` too. – John Szakmeister Feb 13 '13 at 10:25
  • @jszakmeister Yes, reglog (30 days default) but what do you think about >> git log mybranch --not $(git for-each-ref --format='%(refname)' refs/heads/ | grep -v "refs/heads/mybranch") I updated my question a couple of hours ago including this details – dimirc Feb 13 '13 at 22:47
  • @dimirc that's essentially what the script I presented above does. :-) – John Szakmeister Feb 14 '13 at 00:39
  • This did not for me (tried with $1 = trunk/master and dev_branch), it showed commits that already existed in master. Frederic Nault's answer did work. `git checkout dev_branch && git log $(git merge-base trunk/master dev_branch)..HEAD` – Kevin Aug 15 '14 at 17:57
  • @kevinf I don't think you need the merge-base invocation there. `git log master..` should do the trick for you. – John Szakmeister Aug 15 '14 at 20:00
4

This will output the commits on the current branch. If any argument is passed, it just outputs the hashes.

git_show_all_commits_only_on_this_branch

#!/bin/bash
function show_help()
{
  ME=$(basename $0)
  IT=$(cat <<EOF
  
  usage: $ME {NEWER_BRANCH} {OLDER_BRANCH} {VERBOSE}
  
  Compares 2 different branches, and lists the commits found only 
  in the first branch (newest branch). 

  e.g. 
  
  $ME         -> default. compares current branch to master
  $ME B1      -> compares branch B1 to master
  $ME B1 B2   -> compares branch B1 to B2
  $ME B1 B2 V -> compares branch B1 to B2, and displays commit messages
  
  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

# Show commit msgs if any arg passed for arg 3
if [ "$3" ]
then
  OPT="-v"
fi

# get branch names
OLDER_BRANCH=${2:-"master"}
if [ -z "$1" ]
then
  NEWER_BRANCH=$(git rev-parse --abbrev-ref HEAD)
else
  NEWER_BRANCH=$1
fi

if [ "$NEWER_BRANCH" == "$OLDER_BRANCH" ]
then
  echo "  Please supply 2 different branches to compare!"
  show_help
fi

OUT=$(\git cherry $OPT $OLDER_BRANCH $NEWER_BRANCH)

if [ -z "$OUT" ]
then
  echo "No differences found. The branches $NEWER_BRANCH and $OLDER_BRANCH are in sync."
  exit;
fi

if [ "$OPT" == "-v" ]
then
  echo "$OUT"
else
  echo "$OUT" | awk '{print $2}'
fi
Community
  • 1
  • 1
Brad Parks
  • 54,283
  • 54
  • 221
  • 287
  • 1
    This produced about 100 irrelevant log messages for me. – Arlie Stephens Mar 12 '18 at 22:45
  • Hey @ArlieStephens - I tried it and it seemed to work for me still? I just added help and more error messages to the above. Let me know how it goes! – Brad Parks Mar 13 '18 at 12:12
  • Interesting. Looking at the code as you have it now, I think it may be as simple as the original assuming that the branch I care about came off of master. With the current code, I could have specified the parent branch explicitly. (IIRC, my dev branch came from a release branch, not master.) – Arlie Stephens Mar 13 '18 at 16:44
  • @ArlieStephens - yeah I don't think I changed any functionality.... I updated it one more time and made the docs a bit more exact after some local testing... and added a "verbose" option that was there before, but I didnt doc it – Brad Parks Mar 13 '18 at 17:12
4
git rev-list --exclude=master --branches --no-walk

will list the tips of every branch that isn't master.

git rev-list master --not $(git rev-list --exclude=master --branches --no-walk)

will list every commit in master's history that's not in any other branch's history.

Sequencing is important for the options that set up the filter pipeline for commit selection, so --branches has to follow any exclusion patterns it's supposed to apply, and --no-walk has to follow the filters supplying commits rev-list isn't supposed to walk.

jthill
  • 42,819
  • 4
  • 65
  • 113
2

I found this approach relatively easy.

Checkout to the branch and than

  1. Run

    git rev-list --simplify-by-decoration -2 HEAD
    

This will provide just two SHAs:

1) last commit of the branch [C1]

2) and commit parent to the first commit of the branch [C2]

  1. Now run

    git log --decorate --pretty=oneline --reverse --name-status <C2>..<C1>
    

Here C1 and C2 are two strings you will get when you run first command. Put these values without <> in second command.

This will give list of history of file changing within the branch.

Mustkeem K
  • 5,524
  • 1
  • 24
  • 38
  • If you read the first three lines of the question, you'll see that author lists this exact command and explains why it is not what they are looking for – black_fm Jul 18 '18 at 10:36
  • Ah @black_fm I made some changes in answer. You can up vote it if you find it useful now. :) – Mustkeem K Jul 18 '18 at 11:00
1

In my situation, we are using Git Flow and GitHub. All you need to do this is: Compare your feature branch with your develop branch on GitHub.

It will show the commits only made to your feature branch.

For example:

https://github.com/your_repo/compare/develop...feature_branch_name

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
peterpengnz
  • 5,517
  • 2
  • 18
  • 18
  • 1
    I agree that when you get into the situation "I want to see what a pull request will look like", the best solution is often to ignore `git` and instead use GitHub to just make a pull request or compare the branches. – pkamb Apr 12 '18 at 18:46
1

I needed to export log in one line for a specific branch.

So I probably came out with a simpler solution.
When doing git log --pretty=oneline --graph we can see that all commit not done in the current branch are lines starting with |

So a simple grep -v do the job:
git log --pretty=oneline --graph | grep -v "^|"

Of course you can change the pretty parameter if you need other info, as soon as you keep it in one line.

You probably want to remove merge commit too.
As the message start with "Merge branch", pipe another grep -v and you're done.

In my specific ase, the final command was:
git log --pretty="%ad : %an, %s" --graph | grep -v "^|" | grep -v "Merge branch"

1

Here I present an alias based on Richard Hansen's answer (and Ben C's suggestion), but that I adapted to exclude tags. The alias should be fairly robust.

# For Git 1.22+
git config --global alias.only '!b=${1:-$(git branch --show-current)}; git log --oneline --graph "heads/$b" --not --exclude="$b" --branches --remotes #'
# For older Git:
git config --global alias.only '!b=${1:-$(git symbolic-ref -q --short HEAD)}; b=${b##heads/}; git log --oneline --graph "heads/$b" --not --exclude="$b" --branches --remotes #'

Example of use:

git only mybranch  # Show commits that are in mybranch ONLY
git only           # Show commits that are ONLY in current branch

Note that ONLY means commits that would be LOST (after garbage collection) if the given branch was deleted (excluding the effect of tags). The alias should work even if there is unfortunately a tag named mybranch (thanks to prefix heads/). Note also that no commits are shown if they are part of any remote branch (including upstream if any), in compliance with the definition of ONLY.

The alias shows the one-line history as a graph of the selected commits.

  a --- b --- c --- master
   \           \
    \           d
     \           \
      e --- f --- g --- mybranch (HEAD)
       \
        h --- origin/other

With example above, git only would show:

  * (mybranch,HEAD)
  * g
  |\
  | * d
  * f 

In order to include tags (but still excluding HEAD), the alias becomes (adapt as above for older Git):

git config --global alias.only '!b=${1:-$(git branch --show-current)};  git log --oneline --graph --all --not --exclude="refs/heads/$b" --exclude=HEAD --all #'

Or the variant that includes all the tags including HEAD (and removing current branch as default since it won't output anything):

git config --global alias.only '!git log --oneline --graph --all --not --exclude=\"refs/heads/$1\" --all #'

This last version is the only one that really satisfies the criteria commits-that-are-lost-if-given-branch-is-deleted, since a branch cannot be deleted if it is checked out, and no commit pointed by HEAD or any other tag will be lost. However the first two variants are more useful.

Finally, the alias does not work with remote branches (eg. git only origin/master). The alias must be modified, for instance:

git config --global alias.remote-only '!git log --oneline --graph "$1" --not --exclude="$1" --remotes --branches #'
fuujuhi
  • 133
  • 1
  • 8
0

Option 1 (seems faster but you may get the error bash: /mingw64/bin/git: Argument list too long in Git Bash (it worked in Linux though) depending on how many branches your project has)

git log <your_branch> --not $(git branch -a | grep -v <your_branch> | grep -ve '->' | sed "s/\s//g")

Option 2

git rev-list <your_branch> | git name-rev --stdin | sed -E 's/~[0-9]+//g; s/\^[0-9]+//g' | grep " <your_branch>" | awk -F " " '{print $1}'

Leponzo
  • 298
  • 4
  • 15