#!/bin/bash src=$(realpath $(dirname $BASH_SOURCE)/../../) base=$src/libraries declare -A header_dirs arg_verbose=false arg_create_commits=false usage(){ cat < with the path relative to libraries folder. The output is a log of the process. Options: -h,--help Display this help message. -v,--verbose Not only log errors and warnings but also substitutions. -c,--create-commits Create commits in the end. --commit Assume that the user have run the substitutions beforehand - only create the commits. EOF } create_commits(){ for f in $(git diff-files --name-only); do if [[ ${f%%/*} == "libraries" ]]; then echo $f | sed "s,\(libraries/[^/]*\)/.*,\1," else echo ${f%%/*} fi done | uniq | while read d; do if [[ $d == libraries/* ]]; then commit_base=${d#libraries/} else commit_base=$d fi cat >/tmp/commit_msg <' with the path relative to libraries folder. Some of the advantages of such approach: - Only one search path for libraries headers. - OSs like Windows may have a better lookup time. EOF git add -u $d git commit -F /tmp/commit_msg done } replace_include(){ local file=$1 local n=$2 local new_path=$3 local old_path=$4 local regex="\(#\s*include\s*\)[<\"].\+[>\"]" [[ $new_path == $old_path ]] && return $arg_verbose && echo "$file:$n: $old_path --> $new_path" if ! sed -i "${n}s,$regex,\1$new_path," $file; then echo Error on executing command: sed -i "${n}s,$regex,\1$new_path," $file >&2 kill -SIGINT $$ fi } fix_includes(){ local file=$1 local header=$2 local dirs=(${header_dirs[$header]}) local num_dirs=${#dirs[@]} local regex="^\s*#\s*include\s*[<\"]\(.*/\)\?$header[>\"]" grep -ahno $regex $file | while IFS=":" read n match; do path=$(echo $match | sed "s/^\s*#\s*include\s*//g") delim=${path:0:1} path=${path:1:(${#path}-2)} file_dir=$(realpath $(dirname $file)) if [[ $delim == "\"" ]]; then localpath=$file_dir/$path if [[ -f $localpath ]]; then # verify if file is under to the file dir localpath=$(realpath $localpath) [[ $localpath == $file_dir* ]] && continue # if not under file dir, check if $localpath is under $base if [[ $localpath == $base* ]]; then new_path=${localpath#$base/} replace_include $file $n \<$new_path\> \"$path\" continue fi fi fi match_count=0 possible_paths=() for dir in "${dirs[@]}"; do if [[ $dir/$header == *$path ]]; then ((match_count++)) new_path=$dir/$header possible_paths[${#possible_paths[@]}]=$new_path fi done if [[ $match_count -eq 0 ]]; then echo "$file:$n: couldn't find a match for inclusion of $path" elif [[ $match_count -eq 1 ]]; then # check if included header is under file dir if [[ -f $file_dir/$path ]]; then new_path=\"$(realpath $file_dir/$path --relative-to $file_dir)\" else new_path=\<$new_path\> fi if [[ $delim == '"' ]]; then path=\"$path\"; else path=\<$path\>; fi replace_include $file $n $new_path $path else echo "$file:$n: more than one match for inclusion of $path" echo " possible paths:" for p in "${possible_paths[@]}"; do echo " $p" done fi done } trap_reset_tree(){ echo echo Process killed or interrupted! Reseting tree... git -C $src reset --hard exit 1 } # parse args while [[ -n $1 ]]; do case "$1" in -h|--help) usage exit 0 ;; -v|--verbose) arg_verbose=true ;; -c|--create-commits) arg_create_commits=true ;; --commit) create_commits exit $? ;; *) usage >&2 exit 1 ;; esac shift done trap trap_reset_tree SIGINT SIGKILL if ! git -C $src diff-files --quiet --exit-code; then echo You have unstaged changes, please commit or stash them beforehand >&2 exit 1 fi pushd $src > /dev/null # collect all headers git -C $base ls-files *.h > /tmp/headers total=$(cat /tmp/headers | wc -l) header_max_len=0 while read f; do header=$(basename $f) dir=$(dirname $f) if [[ -z ${header_dirs[$header]} ]]; then header_dirs[$header]=$dir else header_dirs[$header]+=" $dir" fi printf "\rCollecting header files paths... $((++i))/$total" >&2 [[ ${#header} -gt $header_max_len ]] && header_max_len=${#header} done \"]" printf "\r($((++i))/$total) Fixing includes for header %-${header_max_len}s" $header >&2 # for each file that includes $header git grep -l $regex | while read f; do fix_includes $f $header done done $arg_create_commits && create_commits popd > /dev/null