#!/usr/bin/env bash
# aufs-clashes
# Author: lcpterid, 2026
# License: GPL v3

stack_maker_prog="aufs-list"
prog_name="$(basename "$0")"
nu="[0-9]"
help_text="Syntax: $prog_name [OPTIONS] reference_branch_number [comparison_branch_number]

Finds all name clashes between file paths (including symlinks/pipes/sockets)
in the two numbered branches.
If two numbers are given, the lower number becomes the reference branch.
Clashes between a file in the reference branch and a directory in the
comparison branch are also considered.
If comparison_branch_number is not given, then reference_branch_number is
compared against all branches below it (numerically higher) by default.
Find branch numbers with ${stack_maker_prog}.

Options:
-a:       Show clashes with all branches, including above the reference branch,
          if comparison branch is not specified.
-d:       Show directory-directory clashes. There will be a lot of these!
-s:       Short output: comparison branch number and mounted file path.
          This is the default.
-l:       Long output: comparison branch number, comparison source path, reference source path.
-q:       Quiet the progress messages before each comparison.
"

script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &>/dev/null && pwd )"
lib_path="$(realpath -m "${script_dir}/../lib/libaufs-dir-tools")"
if ! [ -x "$lib_path" ]; then
       echo "Couldn't find library file!" >&2
       exit 1
fi
. "$lib_path"

[ "$#" -eq 0 ] && error_quit 1 ""
[ "$#" -gt 4 ] && error_quit 1 ""
mount_check || error_quit 2 "AUFS mounts do not match requirements. This tool requires one AUFS branch system, mounted on root (/) and optionally sub-mounted elsewhere."
branch_files_check || error_quit 2 "could not find AUFS branch files in /sys/fs/. Are you on an AUFS file system?"

output_type="short"
progress_messages="yes"
include_branches_above="no"
include_directory_clashes="no"

while getopts ":hadqsl" thisopt; do
	case "$thisopt" in
		h)
			error_quit 0 ""
			;;
		a)
			include_branches_above="yes"
			;;
		d)
			include_directory_clashes="yes"
			;;
		q)
			progress_messages="no"
			;;
		s)
			output_type="short"
			;;
		l)
			output_type="long"
			;;
		?)
			error_quit 1 "invalid option -$OPTARG"
			;;
	esac
done

shift $(( OPTIND - 1 ))

! [[ "$1" =~ ^$nu+$ ]] && error_quit 3 "branch_number must be a positive integer"
if [ "$#" -eq 2 ]; then
	[[ "$2" =~ ^$nu+$ ]] || error_quit 3 "comparison_branch_number must be a positive integer"
	[ "$2" -eq "$1" ] && error_quit 4 "branch numbers must not be equal"
fi

user_branch_number="$1"

if [ "$#" -eq 2 ]; then
	if [ "$2" -lt "$1" ]; then
		user_branch_number="$2"
		comparison_branch_number="$1"
	else
		comparison_branch_number="$2"
	fi
fi

aufs_root="$(find /sys/fs/aufs -type d -name 'si*' -maxdepth 1 | tail -n 1)"
user_branch_pseudofile="${aufs_root}/br${user_branch_number}"
[ -n "$comparison_branch_number" ] && comparison_branch_pseudofile="${aufs_root}/br${comparison_branch_number}"

! [ -f "$user_branch_pseudofile" ] && error_quit 5 "branch number $user_branch_number not found"
[ -n "$comparison_branch_pseudofile" ] && ! [ -f "$comparison_branch_pseudofile" ] &&
	error_quit 5 "branch number $comparison_branch_number not found"

if [ "$include_directory_clashes" = "yes" ]; then
	user_branch_file_types='f,l,p,s,d'
else
	user_branch_file_types='f,l,p,s'
fi

user_branch_root="$(sed 's/=..\(+[a-z_]*\)\?$//' "$user_branch_pseudofile")"
user_branch_files="$(find "$user_branch_root" -type "$user_branch_file_types" |
	sed "s*^${user_branch_root}**" |
	sort |
	delete_sources)"

if [ -n "$comparison_branch_number" ]; then
	all_branch_numbers="$comparison_branch_number"
else
	all_branch_numbers="$(find /sys/fs/aufs/si*/ -name "br[0-9]*" -print0 | xargs -0 -n 1 basename | sed 's/^br//' | sort -n)"
fi

if [ "$include_branches_above" = "yes" ]; then
	operand="-ne"
else
	operand="-gt"
fi

while read -r brno; do
	if [ "$brno" "$operand" "$user_branch_number" ]; then

		[ "$progress_messages" = "yes" ] && echo2 "$(tput setaf 12)$(tput bold)Checking branch ${brno}... $(tput sgr0)"

		lower_branch_root="$(sed 's/=..\(+[a-z_]*\)\?$//' "${aufs_root}/br${brno}")"
		lower_branch_files="$(find "$lower_branch_root" -type 'f,d,l,p,s' |
			sed "s*^${lower_branch_root}**" |
			sort |
			delete_sources)"
		common_lines="$(comm -12 <(echo "$user_branch_files") <(echo "$lower_branch_files"))"

		while read -r brfile; do
			if [ -n "$brfile" ]; then
				[ "$output_type" = "long" ] && printf "%3d\t%s\t%s\n" "$brno" "${lower_branch_root}${brfile}" "${user_branch_root}${brfile}"
				[ "$output_type" = "short" ] && printf "%3d\t%s\n" "$brno" "${brfile}" 
			fi
		done < <(echo "$common_lines")
	fi
done < <(echo "$all_branch_numbers")

