Reputation: 7390
I am using a CI/CD system to automate the building of Docker Images from a git repository. The Image Tag of the image corresponds to the short (i.e. 8-characters) hash of the corresponding git commit, e.g. myimage:123456ab
.
The repository contains source code that gets packaged in the Docker Image and stuff like documentation and deployment configuration that is excluded using a .dockerignore
file (similar to .gitignore
).
While the process works in general, it leads to rebuilding and redeploying Docker Images that are absolute identical, because the only changes were made to files that did not become part of the Image (e.g. the repositories README).
Using only the shell (bash in this case), git and standard *nix tools, is there a way to get the short hash of the latest commit that changed a file which is not ignored by the .dockerignore
file? This should as well cover removing a non-ignored file.
Upvotes: 1
Views: 476
Reputation: 19675
#!/usr/bin/env bash
declare -a ign_table=()
# Populates ign_table with patterns from .dockerignore
while IFS= read -r line || [[ ${line} ]]; do
ign_table+=("${line}")
done < <(sed '/^#/d;/^$/d' .dockerignore)
is_docker_ignored() {
locale -i ignore=1 # false, default not ignored
for ign_patt in "${ign_table[@]}"; do
# If pattern starts with ! it is an exception rule
# when filename match !pattern, do not ignore it
# shellcheck disable=SC2053 # $ign_patt must not use quotes to match wildcards
if [[ ${ign_patt} =~ ^\!(.*) ]] && [[ ${1} == ${BASH_REMATCH[1]} ]]; then
return 1 # false: no need to check further patterns, file not ignored
fi
# Normal exclusion pattern, if file match,
# shellcheck disable=SC2053 # $ign_patt must not use quotes to match wildcards
if [[ ${1} == $ign_patt ]]; then
ignore=0 # true: it match an ignore pattern, file may not be ignored if it later matches an exception pattern
fi
done
return "${ignore}"
}
while IFS= read -r file
do
is_docker_ignored "${file}" && continue # File is in .dockerignore
commit_hash="$(git rev-list --all -1 "${file}")"
printf '%s\n' "${commit_hash:0:8}"
done < <(git ls-files)
Upvotes: 1
Reputation: 2947
You can do this through a combination of git log
and git show
.
The following script will go backwards through the revision history and find the first commit to have a change that would not be ignored by .dockerignore
for commit in $(git log --pretty=%H)
do
# Get the changed file names for the commit.
# Use `sed 1d` to remove the first line, which is the commit description
files=$(git show $commit --oneline --name-only | sed 1d)
if docker-check-ignore $files
then
echo $commit
exit 0
fi
done
exit 1
And then you could define docker-check-ignore
as a script like the following:
#!/bin/sh
DIR=$(mktemp -d)
pushd $DIR
# Set up a temporary git repository so we can use
# git check-ignore with .dockerignore
git init
popd
cp .dockerignore $DIR/.gitignore
pushd $DIR
git check-ignore $@
# Store the error code
ERROR=$?
popd
rm -rf $DIR
exit $ERROR
I will leave reducing the number of file system operations rather than creating/removing a directory for each commit.
Upvotes: 2