140

I'm trying to write a fabric script that does a git commit; however, if there is nothing to commit, git exits with a status of 1. The deploy script takes that as unsuccessful, and quits. I do want to detect actual failures-to-commit, so I can't just give fabric a blanket ignore for git commit failures. How can I allow empty-commit failures to be ignored so that deploy can continue, but still catch errors caused when a real commit fails?

def commit():
    local("git add -p && git commit")
kojiro
  • 74,557
  • 19
  • 143
  • 201

6 Answers6

241

Catch this condition beforehand by checking the exit code of git diff-index?

For example (in shell):

git add -A
git diff-index --quiet HEAD || git commit -m 'bla'

EDIT: Fixed git diff command according to Holger's comment.

participant
  • 2,923
  • 2
  • 23
  • 40
Tobi
  • 78,067
  • 5
  • 32
  • 37
  • 78
    Note that `git diff` is a "porcelain" command that should not be used for scripting. What you most likely want is `git diff-index --quiet HEAD || git commit -m 'bla'`. See also [this answer](http://stackoverflow.com/a/2659808/659002). – Holger Feb 14 '13 at 01:19
  • 1
    To explain things further, the problem with `git diff --quiet --exit-code --cached` is that it will evaluate to `1` (false) only for modified files that have not been staged for commit (unadded files). The up-voted comment is the best solution to account for new files and deletions. – Jorge Bucaran Dec 10 '14 at 09:21
  • 2
    The comment about `git diff-index --quiet HEAD || git commit -m 'bla'` should be an answer in this question. – Rakib Sep 27 '16 at 11:52
  • 2
    Since Tobi didn't care to fix his answer according to Holger's comment, I edited his answer myself. – vog Jun 07 '17 at 16:25
  • Note that git diff-index --quiet HEAD does not test if the local repository is up-to-date with the origin. – bortzmeyer Apr 23 '18 at 10:42
  • 2
    Can someone explain what exactly is returned by "git diff-index --quiet HEAD"? Because on my system, it returns "$?=1" both when there are files staged for commit, and when there are no files staged for commit. – Erel Segal-Halevi Feb 28 '19 at 07:18
  • 2
    It seems to me that `git diff-index --quiet --cached HEAD` is the correct answer. The current answer, `git diff-index --quiet HEAD` (no `--cached`), will not produce the correct result if there are differences only between the index and work tree, in which case `git commit` will fail. – Vladimir Panteleev May 08 '20 at 15:33
  • fyi: a related application of this concept: "git mid-edit saves": https://gist.githubusercontent.com/johnnyutahh/e8cfb515fb65635dca520117477456b8/raw – Johnny Utahh Apr 17 '22 at 23:13
81

From the git commit man page:

--allow-empty
    Usually recording a commit that has the exact same tree as its
    sole parent commit is a mistake, and the command prevents you
    from making such a commit. This option bypasses the safety, and
    is primarily for use by foreign SCM interface scripts.
participant
  • 2,923
  • 2
  • 23
  • 40
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
16

Just extending Tobi & Holger's answer with an explicit if statement.

git add -A
if ! git diff-index --quiet HEAD; then
  git commit -m "Message here"
  git push origin main
fi

Let's give it a bit explanation.

  1. git add -A: staged your changes (required for the next step)

  2. git diff-index --quiet HEAD will compare your staged changes with the HEAD.

    --quiet is impotant as it will imply --exit-code which "makes the program exit with codes 1 if there were differences and 0 means no differences".

See --quiet.

ninhjs.dev
  • 7,203
  • 1
  • 49
  • 35
3

When going through a shell, you can use the ... || true technique to declare a failure to be expected and ignored:

git commit -a -m "beautiful commit" || true

This would also prevent a shell script from exiting when using the errexit option.

Instead of ... || true you can also use any other command that exits with a return code of 0, such as

git commit -a -m "beautiful commit" || echo "ignore commit failure, proceed"
Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130
3
with settings(warn_only=True):
  run('git commit ...')

This causes fabric to ignore the failure. Has the advantage of not creating empty commits.

You can wrap it in a additional layer of with hide('warnings'): to totally suppress output, otherwise you'll get a note in the fabric output that the commit failed (but the fabfile continues to execute).

Tyler Eaves
  • 12,879
  • 1
  • 32
  • 39
  • 6
    OP wrote "I do want to detect *actual* failures-to-commit"; this code will hide *all* failures-to-commit. – bfontaine Dec 19 '16 at 15:16
-7

try/catch baby!

from fabric.api import local
from fabric.colors import green


def commit(message='updates'):
    try:
        local('git add .')
        local('git commit -m "' + message + '"')
        local('git push')
        print(green('Committed and pushed to git.', bold=False))
    except:
        print(green('Done committing, likely nothing new to commit.', bold=False))
devpascoe
  • 5
  • 3
  • 13
    To explain why you get downvoted: There can be other errors which you want to catch. You don't want to just assume that in case of an error, it might be that nothing has to be committed. -- Also, but that's unrelated: Never use a generic `except:`, use `except Exception` or so instead. – Albert Mar 21 '14 at 08:40