Bash Script - Determine whether to add file to Git commit [duplicate] - bash

This question already has answers here:
How do I set a variable to the output of a command in Bash?
(15 answers)
Closed 5 months ago.
This is probably a simple one for a bash scripter, which I am not.
I'm running a cron job that downloads some data, and then depending on that data, may or may not modify a second file. After the job, I want to git commit one or both files. For the conditional commit, I tried this in a .sh script:
# attempt to capture whether MyNotes.txt was changed
# by counting lines in git status output
mywc=(git status -s MyNotes.txt | wc -l)
echo $mywc found!
if [ $mywc = 1 ]; then
echo Add file for commit
else
echo Nothing to add
fi
I'm pretty much getting nowhere; this thing seems to fail on the first line with syntax error near unexpected token '|'. If I run git status -s MyNotes.txt | wc -l on the command line, I get the numeric output I expect.
What am I doing wrong and how can I make this work?
If there's a more elegant way to determine whether a file changed, feel free to share.
Also, for my edification, how could I get this to work without the interim mywc variable? I.e., if I wanted to just do the command within the if, something like this:
if [[ $(git status -s MyNotes.txt | wc -l) = 1 ]]; then
...
Thanks!

What am I doing wrong and how can I make this work?
put a dollar before parenthesis.
foo=$(command)
The thing you are using looks like a bash array
declare -a letters=(a b c d)
If there's a more elegant way to determine whether a file changed, feel free to share.
Consider this:
$ git diff -s --exit-code README.md || echo has changed
has changed
$ git checkout README.md
Updated 1 path from the index
$ git diff -s --exit-code README.md || echo has changed
The OR (||) runs if the first command exits with a non-zero code.
Same thing essentially:
$ false || echo false exits with 1
false exits with 1
$ true || echo will not trigger
An aspect of bash that people overlook is that [[, ]], [ and ] are separate commands. They have return codes too. With this knowledge, you can leverage the return codes with if and any other command.
$ if true; then echo yes; else echo no; fi
yes
$ if false; then echo yes; else echo no; fi
no
So for detecting changes in a tracked file:
$ if git diff -s --exit-code README.md; then echo same as in index; else echo changed; fi
same as in index
$ echo 123 >> README.md
$ if git diff -s --exit-code README.md; then echo same as in index; else echo changed; fi
changed
With all of that said...
Just add the file. You don't need to check anything. If it hasn't changed, nothing will happen.
$ echo foo >> myfile
$ git add myfile
$ git commit -m 'maybe changed' myfile
[master b561cc1] maybe changed
1 file changed, 1 insertion(+), 1 deletion(-)
$ git add myfile
$ git commit -m 'maybe changed' myfile
no changes added to commit (use "git add" and/or "git commit -a")
if you need to avoid a non-zero exit code (such as with set -e), just put a || true after the command that you want to ignore the exit status of:
$ cat foo.sh
#!/bin/basho
set -e
echo foo >> myfile
git add myfile
git commit -m 'maybe changed' myfile
git add myfile
git commit -m 'maybe changed' myfile > /dev/null || true
echo no error here. it\'s fine..
false
echo fill never reach this.
Try running that script and see what happens

I search for a way for checking if file changed.
git diff --exit-code -s <path>
Now the bash scripter knows that every command returns a status code which can be checked with $?. In case everything went smoothly, 0 is returned. In that case we get 0 if file is not changed.
Every bash scripter knows too that you can use that with && and || operators (because of lazy evaluation) to write such construct:
git diff --exit-code -s <path> && echo "should add file"
About your edification, what you wrote is perfectly fine!

As CryptoFool pointed out in a comment, I failed to include a $ in my variable assignment. Simple fix in the first line of my script:
mywc=$(git status -s MyNotes.txt | wc -l)
As matt pointed out in a subsequent comment, doing a git add on a file that hasn't changed has no effect. It won't stage the file for commit. So instead of doing conditional logic to determine whether to git add myfile.txt, I'll just blindly execute git add myfile.txt, which will either stage the file if there are changes, or do nothing if there are no changes. Therefore, my entire script can be replaced with one line:
git add MyNotes.txt

Related

How to run another git command only if ”git diff“ outputs something?

How can I run a command in bash or any other bash-like shell only if another command outputs something?
I am working on an API that updates the cache storage of a tool and commits the updated cache to GitHub. What I want to do is, after rebuilding the cache, I want to check if git diff HEAD ./path/to/cache outputs something and if it does, then I want to run git add ./path/to/cache.
Unfortunately, this doesn't work:
git diff HEAD ./path/to/cache && git add ./path/to/cache
I think because the first command doesn't return false or exit and instead outputs an empty string or nothing at all. So, what's the correct way to achieve this in bash?
Use the --exit-code option, which causes git diff to exit with 0 if there are no diffs and 1 otherwise. Then you can ignore the output.
git diff --exit-code HEAD ./path/to/cache || git add ./path/to/cache
The answer posted by #chepner is definitely the best for my use case. But my question was originally for any use case. If you want to achieve the same behavior for any other commands, you can use the grep command to check if the first command outputs anything.
git diff HEAD ./path/to/cache | grep -q . && git add ./path/to/cache
[ -z $(git diff HEAD ./path/to/cache) ] || git add ./path/to/cache
# [ EXPRESSION ], -z the length of STRING is zero.
# || If no, execute subsequent shell

git run command if nothing to commit

This question is probably about bash as much as it is about git.
How do I run a command if there is nothing to commit? I did the following, but it runs the echo hello command even if there is nothing to commit:
if git diff --exit-code; then
echo hello
fi
Coming from a python background, what I assumed would happen is if git ... command is empty then the stuff inside the if statement would not execute. I have confirmed that git diff --exit-code returns nothing.
TLDR: Your current code echoes hello in the success case, not the failure case.
We can fix this by inverting the condition, so it becomes:
if ! git diff --exit-code; then
echo hello
fi
I'd recommend having a look at How to check the exit status using an 'if' statement which provides a few options for how to do this.
Try something like this:
git diff --exit-code # exit code will be "0" if no changes
if [ $? -gt 0 ]; then
echo hello
fi

bash, script to git add one file and commit

I'm trying to create a script to do this:
git add "file"
git commit -m "Comment"
My idea is to run:
gac "file" "Comment"
I know I can do something similar but for all files, with:
echo 'alias gac="/path/to/gitaddcommit.sh"' >> ~/.bash_profile
And the .sh would be:
!/bin/bash
git add .
echo “Enter commit message: “
git commit -am “$commitMessage”
Well you need two things :
A bin folder where you can put every sh script you want to use everywhere.
More knowledge about shell scripting and how you can get argv (in your ex: 'file' 'Comment')
So first go to your /home/<username> then mkdir bin && cd bin && pwd
then copy the pwd and add it into your PATH env variable inside your .bashrc
path example: PATH='/bin/:/sbin/:/home//bin
Then source ~/.bashrc you can now use every sh script inside you bin folder everywhere.
Cool so first problem done !
you don't have to do echo alias gac="/path/to/gitaddcommit.sh"' >> ~/.bash_profile anymore.
Now second problem here a post that can help you post
And let me show you for your example :
cd ~/bin && vi gac.sh
Now the script :
#!/bin/sh
if [ "$#" -ne 2 ]; then
echo "Usage: ./gac FILENAME COMMIT_MESSAGE" >&2
exit 1
fi
git add "$1"
git commit -am "$2"
First we check the number or arg then git add and commit.
Simple and fast maybe checking if arg one is a file might be a good idea too.
PS: i'm going to re write my post ahah
Here's what I have in my .bashrc:
ga ()
{
if test "$1" != "-f" && git rev-parse HEAD > /dev/null 2>&1 && ! git diff-index --quiet HEAD; then
echo 'Repo is dirty. -f to force' 1>&2;
return 1;
fi;
git add "$#";
list=$(git diff --name-only --cached | tr \\n \ );
git commit -m "Add $list"
}
The commit message is autogenerated, but you could easily modify it to prompt the user or take it from somewhere else.

condition always returns false

Below is my pre-commit git hoook
#!/bin/bash
....
# if git diff -U0 "$FILE_PATH" | grep -iq 'todo'; # Double quoting $FILE_PATH doesnt' change anything
if git diff -U0 $FILE_PATH | grep -iq 'todo';
then
echo $FILE_PATH ' -> Contains TODO'
exit 1
else
echo 'nooooooooooooooooooooooooooooooooooo'
fi
I'm always getting the noooooooooooooooooooo message, however the command below, tried directly on my terminal, works well:
git diff -U0 my/file/path.php | grep -iq 'todo' && echo 'true' || echo 'false'
Output
true
UPDATE
When running bash .git/hooks/pre-commit it works, very strange!!
FYI
I don't know if it's an important information but .git/hooks/pre-commit is a symbolik link
Most likely, your pipe does not return status 0. To verify that this is the case (and not the way you write your compound statement), you could rewrite it as
git diff -U0 "$FILE_PATH" | grep -iq 'todo'
grep_status=$?
echo grep status is $grep_status
if (( grep_status == 0 ))
then
echo contains todo
else
echo no
fi
I also noticed that your code contains an unnecessary semicolon in the if line. I first thought that this semicolon might cause the weird behaviour, but at least on my bash, where I tried your code, it does not seem to do any harm. Still, I would remove it for the safe side.

Use forward slash in variable

I wrote a script to ease the syncing and building of Android source. I tried adding a function to cherrypick patches, but I can't get it to work properly. I know it's because of the forward slashes, but I don't know how to protect/escape them.
Part of the code is:
echo "Copy/paste the project folder, i.e. 'frameworks/base'"
read folder
echo ""
echo "Now paste the cherry-pick git link, i.e. 'git fetch <someproject> refs/changes/... && git cherry-pick FETCH_HEAD'"
read cherry
echo ""
Begin
clear
echo ""
export IFS="&&"
for x in $cherry
do
cd ${CM}/${folder}
CHERRY=$(trim "$x")
$CHERRY
done
Let's say that the 'cherry' variable is:
git fetch http://r.cyanogenmod.com/CyanogenMod/android_frameworks_base refs/changes/68/22968/2 && git cherry-pick FETCH_HEAD
I would get this error:
/home/tristan202/bin/build_cm.sh: line 159: git fetch http://r.cyanogenmod.com/CyanogenMod/android_frameworks_base refs/changes/91/23491/2: No such file or directory
/home/tristan202/bin/build_cm.sh: line 159: git cherry-pick FETCH_HEAD: command not found
I cannot figure out why it fails.
The 'trim' function it calls is a function that trims leading and trailing spaces. If I do echo "$CHERRY" within the for loop, the commands are printed correctly, but it still fails.
I will give your another example:
cmd='echo hello && echo world'
$cmd
The result is:
hello && echo world
bash parses the command $cmd as Simple Commands not Lists of Commands.
After Parameter Expansion, && is passed as argument to echo(1st word after Word Splitting).
The solution is pulling && out:
cmd1='echo hello'
cmd2='echo world'
$cmd1 && $cmd2
Once you put && in a variable it ceases to be interpreted as separating two commands:
$ A="echo a && echo b"
$ echo $A
echo a && echo b
$ echo c && ${A}
c
a && echo b
So you need to avoid putting && into a variable.
git was even telling you that the && was the problem in its error message.

Resources