2
\$\begingroup\$

I had searched for a solution to mvn clean install on my changed projects after a git pull. I even asked a question over at SO about the same thing. The answer I got pointed me into creating my own script to achieve what I wanted to accomplish. So I went, I saw, and I conquered the script that I wanted. However, it is probably not efficient, and could use refactoring. I am new to bash scripting and learned a bit while doing this.

The structure I was targeting is a Maven POM Project. That project contains several sub-POM Projects, which in turn contain Maven Java Projects.

Example Structure:

-Parent
|-Sub_Parent_A
 |-Child_1
 |-Child_2
|-Sub_Parent_B
 |-Child_3
 |-Child_4

I wanted to only call mvn clean install if a project had files that changed in the child projects. This would reduce time on going to each project by hand and calling the command or by just calling the command on the parent project.

The code, along with revisions, can also be found on GitHub

################################################################################
#
# License:.....GNU General Public License v3.0
# Author:......CodeMonkey
# Date:........14 November 2018
# Title:.......GitMavenCleanInstall.sh
# Description: This script is designed to cd to a set Maven POM Project,
#   perform a git remote update and pull, and clean install the changed
#   files projects.
# Notice:......The project structure this script was originally set to target
#   is structured as a Maven POM Project that contains several sub-POM Projects.
#   The sub-POM Projects contain Maven Java Application projects. The targets
#   should be easy to change, and allow for others to target other structures.
#
################################################################################
#
# Change History: N/A
#
################################################################################

#!/bin/bash
#Function to check if array has element
containsElement () {
    local e match="$1"
    shift
    for e; do [[ "$e" == "$match" ]] && return 0; done
    return 1
}

#Navigate to the POM Project
cd PATH/TO/POM/PROJECT
#Remote update
git remote update -p
#Pull
git pull

#Get the current working branch
CURRENT_BRANCH="$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')"
#Get the output of the command git diff
GIT_DIFF_OUTPUT="$(git diff --name-status HEAD@{1} ${CURRENT_BRANCH})"

#Split the diff output into an array
read -a GIT_DIFF_OUTPUT_ARY <<< $GIT_DIF_OUTPUT
#Declare empty array for root path
declare -a GIT_DIFF_OUTPUT_ARY_ROOT_PATH=()
FORWARD='/'
#Loop diff output array
for i in "${GIT_DIFF_OUTPUT_ARY[@]}"
do
    #Check that the string is not 1 Character
    if [[ "$(echo -n $1 | wc -m)" != 1 ]]
    then
        #Split the file path by /
        IFS='/' read -ra SPLIT <<< $i
        #Concatenate first path + / + second path
        path=${SPLIT[0]}$FORWARD${SPLIT[1]}
        #Call function to see if it already exists in the root path array
        containsElement "$path" "${GIT_DIFF_OUTPUT_ARY_ROOT_PATH[@]}"
        if [[ $? != 0 ]]
        then
            #Add the path since it was not found
            GIT_DIFF_OUTPUT_ARY_ROOT_PATH+=($path)
        fi
    fi
done

#Loop root path array
for val in ${GIT_DIFF_OUTPUT_ARY_ROOT_PATH[@]}
do
    #CD into root path
    cd $val
    #Maven call to clean install
    mvn -DskipTests=true --errors -T 8 -e clean install
    #CD back up before next project
    cd ../../
done
\$\endgroup\$
  • \$\begingroup\$ @janos I created the code on a standalone computer. I had to retype it, hence the typo. However, git diff --name-status does get that output, which is why the output is split into an array and looped over to insert the root file paths into a different array. Though I did not know of --name-only. That will let me eliminate the if check. \$\endgroup\$ – CodeMonkey Nov 19 '18 at 19:02
  • \$\begingroup\$ You have another typo here: GIT_DIF_OUTPUT. It would be really best if you tested your script, and then copy-paste the fully working version without typos. \$\endgroup\$ – janos Nov 19 '18 at 19:08
  • \$\begingroup\$ @janos Unfortunately, I cannot just copy-paste the script from that computer. The setup of the computer and network is configured in a way that I cannot move files around like that, only an admin of the computer with CD burning rights can do that, of which I am not. \$\endgroup\$ – CodeMonkey Nov 19 '18 at 19:16
  • \$\begingroup\$ You could create a dummy Maven project in a dummy Git repository to test your script. \$\endgroup\$ – janos Nov 19 '18 at 19:18
  • \$\begingroup\$ @janos The script works, I have tested it before and after the suggestion you provided, but moving the script from that computer to an outside network connected one is a no-go from me. That is why it had typos, because I had to sit there and copy by typing. \$\endgroup\$ – CodeMonkey Nov 19 '18 at 19:22
1
\$\begingroup\$

Take a cleaner approach if you can

The current approach is basically a dirty hack to work around Maven's inability to do incremental builds. I recommend to start looking into another more modern build system that is able to re-build just what needs to be rebuilt. Such as Gradle.

Always double-quote variables in arguments

As a rule of thumb, always put double-quotes around variables used as arguments. For example in all these places, there should be double-quotes around the variables:

read -a GIT_DIFF_OUTPUT_ARY <<< $GIT_DIFF_OUTPUT
...

GIT_DIFF_OUTPUT="$(git diff --name-status HEAD@{1} ${CURRENT_BRANCH})"
...

IFS='/' read -ra SPLIT <<< $i
...

GIT_DIFF_OUTPUT_ARY_ROOT_PATH+=($path)
...

for val in ${GIT_DIFF_OUTPUT_ARY_ROOT_PATH[@]}
...

cd $val
...

Avoid performance pitfalls

For each path in the diff, the script builds the array GIT_DIFF_OUTPUT_ARY_ROOT_PATH with the first two path segments, and keeps the elements unique by not adding a value if it's already in the array. As the size of the array grows, this becomes increasingly inefficient, as this is quadratic time complexity.

I believe the paths printed by git diff is sorted by default. You can take advantage of that: track the previous element, and only add a new value when it's different from the previous. You will achieve better performance and without using the containsElement function.

Use the exit code directly in conditional expressions

Instead of this:

containsElement "$path" "${GIT_DIFF_OUTPUT_ARY_ROOT_PATH[@]}"
if [[ $? != 0 ]]
then
    #Add the path since it was not found
    GIT_DIFF_OUTPUT_ARY_ROOT_PATH+=($path)
fi

You could use the exit code of the function directly in a conditional:

if ! containsElement "$path" "${GIT_DIFF_OUTPUT_ARY_ROOT_PATH[@]}"
then
    #Add the path since it was not found
    GIT_DIFF_OUTPUT_ARY_ROOT_PATH+=($path)
fi

Get current Git branch name using native Git commands

Instead of this:

CURRENT_BRANCH="$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')"

This is a better, native way:

CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"

Use native Bash to get string length

This line tries to check the length of a variable:

if [[ "$(echo -n $i | wc -m)" != 1 ]]

Although -m is used, the possible values will not be multibyte, so the solution is equivalent to using -c. In this case, a better solution exists with native Bash:

if [[ ${#i} != 1 ]]

Use better variable names

i is good for simple counters. It's not a good name for anything else.

What is FORWARD? path_separator or pathsep would have been better.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the tips. Script has been updated. Seems to run faster in the initial loop, which will help greatly with bigger code changes across multiple projects. \$\endgroup\$ – CodeMonkey Nov 19 '18 at 23:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.