4

If I run git diff, and my changes are less than one page, the command will automatically exit. This is undesired because this is in a script and I immediately call git commit afterwards. This causes single page changelogs to be missed.

My first thought would be to pipe the differences into less, but this causes no differences changelogs to display an empty screen (which requires a q press to exit). Here is the command I'm using: git diff --color=always | less.

Is there some better way to do this?

fedorqui
  • 275,237
  • 103
  • 548
  • 598
Chris Smith
  • 2,928
  • 4
  • 27
  • 59
  • 1
    Do you have a `.lessrc` file around somewhere? `less` should not be automatically existing by default. (For example, the `-e` and `-E` options to `less` provide the behavior you are reporting.) – chepner Sep 13 '16 at 17:05
  • Ignore the above comment and see my answer; I have an environment variable set that prevents `less` from exiting early. – chepner Sep 13 '16 at 18:17

3 Answers3

5

Here's a script to do the job:

#!/bin/bash

num_lines=$(git diff | wc -l)

if [ $num_lines -eq 0 ]
then
  echo "No changes"
else
  git diff --color=always | less --raw-control-chars
fi

Or, here's a one-liner originally based on @Phillip's comment, with some fixes thanks to @tripleee:

git diff --color=always | (IFS=$'\n' read -r A; if [ -n "$A" ]; then (printf '%s\n' "$A"; cat) | less --raw-control-chars; else echo "No changes"; fi)

The one-liner has the advantage of only running git diff once for better performance.

Some explanation per @tripleee's comment:

You should properly use read -r to not unintentionally mangle the first line. This will still mangle leading or trailing whitespace. You can fix that with IFS=$'\n' before read -r. echo might throw an error or behave strangely if the first line starts with a dash (which looks to echo like an option argument) -- use printf '%s\n' "$A" instead to avoid that.

In both cases, the --raw-control-chars option (short version -r) passed to less will cause the colors to show up correctly.

Scott Weldon
  • 9,673
  • 6
  • 48
  • 67
  • This can be optimized such that you only have to `diff` once: If you run `git diff` with the `PAGER` environment variable set to a similar bash script: `PAGER="read A; if [ -n '\$A' ]; then echo '\$A'; less; fi" git diff`. – Phillip Sep 13 '16 at 16:58
  • @Phillip I wouldn't consider exploiting the pager variable for business logic an optimization, but this is a nice hack for Code Golf. – Hubert Grzeskowiak Sep 13 '16 at 16:59
  • 2
    The optimization is to run `git diff` only once, rather than twice, and dynamically decide whether to run `less` or not. `git diff | (read A; if [ -n "$A" ]; then echo "$A"; less; fi)` would work just as well. – Phillip Sep 13 '16 at 17:03
  • Isn't there a missing `else` before the `less` call, @Phillip? – Hubert Grzeskowiak Sep 13 '16 at 17:12
  • Thanks @Phillip! Added a one-liner based on yours to my answer. – Scott Weldon Sep 13 '16 at 17:13
  • 1
    @HubertGrzeskowiak Nope, that's intentional. If there is any output, then run `less`, else just exit. – Phillip Sep 13 '16 at 17:13
  • 1
    Long term Linux user here and I just learned a whole bunch on bash usage :-) – Hubert Grzeskowiak Sep 13 '16 at 17:14
  • @ScottWeldon Your version of the one-liner drops the first line of the `git diff` output! – Phillip Sep 13 '16 at 17:16
  • @Phillip: I edited to match your version, but that still has a problem: the first line is printed to the terminal, and *everything else* is sent to `less`. – Scott Weldon Sep 13 '16 at 17:23
  • 1
    You should properly use `read -r` to not unintentionally mangle the first line. This will still mangle leading or trailing whitespace. You can fix that with `IFS=$'\n'` before `read -r`. `echo` might throw an error or behave strangely if the first line starts with a dash (which looks to `echo` like an option argument) -- use `printf '%s\n' "$A"` instead to avoid that. – tripleee Sep 13 '16 at 17:35
  • @tripleee: Adding `-r` to the `read` command doesn't seem to help with the line mangling. The first line is still being printed to the terminal instead of being sent to `less`. – Scott Weldon Sep 13 '16 at 17:55
  • The `-r`option very specifically disables some legacy behavior with backslashes. The problem with losing the first line is unrelated; it seems to be the placement of the parenthesis -- the answer completely lacks a closing parenthesis. I would go with `... | IFS=$'\n' read -r; if [ -n "$REPLY" ]; then ( echo "$REPLY"; cat ) | less; fi` – tripleee Sep 13 '16 at 19:52
  • @tripleee: Ah okay, I thought you were responding to my last comment. Missing parenthesis was a typo; fixed. – Scott Weldon Sep 13 '16 at 20:06
  • Oh and I forgot my own `printf` advice, sorry! Duh. – tripleee Sep 13 '16 at 20:09
  • But if you want the output from that to be sent to `less`, it needs to be part of that pipeline. That's why you need parens; before `less`. – tripleee Sep 13 '16 at 20:12
  • @tripleee: I'm still having some issues. If you're willing to help me debug, then let us [continue this discussion in chat](//chat.stackoverflow.com/rooms/123288/discussion-between-scott-weldon-and-tripleee). – Scott Weldon Sep 13 '16 at 20:20
  • 1
    @ScottWeldon Yep, I meant to pipe the `echo` output to `less` as well, like @tripleee did lateron. Sorry. – Phillip Sep 14 '16 at 06:58
  • 1
    I was using this in a makefile, so I had to move it to it's own script file to work properly. – Chris Smith Sep 14 '16 at 16:12
2

What you are seeing is less being invoked with the -F option by git. Normally, less uses any options stored in the environment variable LESS. If that variable is not set, git automatically sets it to FRX before running less. (That is, without a value for LESS, git effectively is running less -FRX.)

One way to override this is to add the following to your .gitconfig file:

[core]
    pager = less -+F

If LESS is not set, then the preceding configuration causes less to be called as

LESS=FRX less -+F

meaning the -F option is first enabled via the environment in which less runs, then immediately disabled from the command line.

(In my comment to your question, I indicated that less should not be exiting early. I observed that because I in fact have LESS=X in my environment, so I did not have -F added automatically.)

chepner
  • 497,756
  • 71
  • 530
  • 681
1

Provided less is your pager, you can affect this behavior for all invocations with

export LESS=-E

This will override any previous value. To avoid that, and instead add -E to the front of the other options, use

LESS=-E"${LESS#-}"

To affect it for just git, maybe something like

alias git='LESS=-E"${LESS#-}" git'

(This is sh syntax, i.e. should be suitable for Bash, ksh, zsh, etc.)

For other pagers, similar approaches should work. You just need to find the option which affects this behavior.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • In practice, I have already used this to add `LESS=-r` to settings when running `git` in order to get color codes to work correctly. Dunno what's up with that, but it seems to be a problem on all platforms where I use `git`. – tripleee Sep 13 '16 at 17:37