Skip to content

update copyright.sh

Martijn Dekker edited this page Apr 25, 2025 · 2 revisions

update-copyright.sh

This script helps maintain proper copyright attribution in the ksh 93u+m code base. It:

  1. Updates the COPYRIGHT file with all the contributors to ksh 93u+m since the reboot, based on the commit history. The authors are sorted by number of commits in descending order and inserted under the first CONTRIBUTORS header in that file. Authors already listed there but who have no commits are sorted at the bottom.
  2. Updates the ksh 93u+m (c) YEAR-YEAR Contributors to ksh 93u+m notices in the copyright headers and --about messages. The second year is updated to the current year.
  3. Adds any missing contributors to each file to that file's copyright header based on that file's commit history.

The script does not stage or commit the changes it makes. 2 and 3 are only done for files that have changed since the new year, including files that have uncommitted or unstaged changes.

set -fCu; IFS=''	# safe mode
PATH=/opt/ast/bin:$PATH	# use path-bound built-ins

error_out()
{
	echo "$0: $@" >&2
	exit 1
}
PATH=/dev/null let ".sh.version >= 20220728" 2>/dev/null || error_out 'requires ksh 93u+m/1.0'

# special-case github's flukes in names of commit authors
# (they were fine in the original commits, then merging/squashing changed them)
function fix_authorname
{
	nameref a=$1
	case $a in
	lev105)		a='Lev Kujawski' ;;
	pghvlaans)	a='K. Eugene Carlson' ;;
	sterlingjensen)	a='Sterling Jensen' ;;
	vmihalko)	a='Vincent Mihalkovic' ;;
	esac
}

# update file if it changed from $tmpfile
function update_file
{
	[[ -s $1 ]] && cmp -s $1 $tmpfile
	case $? in
	0)	;;
	1)	print -r "updating $1"
		cat $tmpfile >|$1 || exit ;;
	*)	error_out "error in cmp" ;;
	esac
}

# centre an author line to 70 characters
function centre_line
{
	nameref l=$1
	((${#l} > 70)) && prinf '%q: WARNING: author line too long: %q\n' "$0" "$l" >&2
	while ((${#l} < 69)); do
		l=" $l "
	done
	((${#l}==69)) && l+=' '
}

[[ -d .git ]] || error_out "run this from the ksh 93u+m git repo's main directory"

git_branch=$(git branch --show-current)
[[ -n $git_branch ]] || error_out "could not get current git branch"
current_year=$(date +%Y)
[[ $current_year =~ ^[0-9]{4}$ ]] || error_out "could not get current year"
first_commit=$(git log --since="$current_year-01-01 00:00 UTC" --pretty=format:'%H' | tail -n1)
[[ $first_commit =~ ^[0-9a-f]{40}$ ]] || error_out "could not get the first commit of $current_year" 
current_commit=$(git log -1 --pretty=format:'%H')
[[ $current_commit =~ ^[0-9a-f]{40}$ ]] || error_out "could not get current commit" 

# get author/email associations from log, then override them with those specified in COPYRIGHT
typeset -A email
git log reboot..$git_branch '--pretty=tformat:%ae %an' | while IFS=' ' read e n; do
	fix_authorname n
	email[$n]=$e
done
set_email=$(awk '{
	if(!state && $0 ~ /CONTRIBUTORS/)
		state=1;
	else if(state==1)
	{
		spacingline=$0;
		state=2;
	}
	else if(state==2)
	{
		if($0==spacingline)
			exit;
		sub(/^#[[:blank:]]*/, "email['\''");
		sub(/ </, "'\'']='\''");
		sub(/>.*/, "'\''");
		print;
	}
}' COPYRIGHT) || exit
eval "$set_email" || exit
unset set_email

tmpfile=${TMPDIR:-/tmp}/update-copyright-years.${$}${RANDOM}
trap 'exec rm -f $tmpfile' EXIT

# update COPYRIGHT file
# get all the authors from the git log, sorted by number of commits in descending order
# (awk cannot portably pass strings with newlines via -v options, so pass as an env var)
export contributors=$(
	{
		git log reboot..$git_branch '--pretty=tformat:%an'
		printf '%s\n' "${!email[@]}" # keep existing names from COPYRIGHT even if they have no commits
	} | while read n; do
		fix_authorname n
		print -r $n
	done | sort | uniq -c | sort -rn | while IFS=' ' read throwaway_number name; do
		c="$name <${email[$name]:-no@email.invalid}>"
		centre_line c
		print -r "#${c}#"
	done
)
awk -v "current_year=$current_year" '{
	if(!state)
	{
		sub(/2020-.... Contributors to ksh 93u\+m/, "2020-" current_year " Contributors to ksh 93u+m");
		print;
		if($0 ~ /CONTRIBUTORS/)
			state=1;
	}
	else if(state==1)
	{
		print;
		spacingline=$0;
		state=2;
	}
	else if(state==2)
	{
		print ENVIRON["contributors"];
		state=3;
	}
	else if(state==3)
	{
		if($0==spacingline)
		{
			print;
			state=4;
		}
	}
	else
		print;
}' COPYRIGHT >|$tmpfile
unset contributors
update_file COPYRIGHT

# update headers in source files
(git diff --name-only; git diff --name-only --cached; git diff-tree --name-only -r $first_commit..HEAD) \
| while read -r file
do
	[[ -f $file ]] || continue
	read -r n <$file
	[[ $n == '/***********************************************************************' \
	|| $n == '########################################################################' ]] \
		|| continue
	sedscript=''
	# exclude version.h from adding author names -- most changes to it are just version increments
	if [[ $file != src/cmd/ksh93/include/version.h ]]
	then
		# comment character
		cc=${n:2:1}
		# find the line number of the last author line (after which to insert new authors)
		n="^[$cc] .*<.*@.*\..*>.* [$cc]$"
		alineno=$(awk -v ere=$n '{ if($0 ~ ere) found++; else if(found) { print NR-1; exit; } }' "$file")
		((alineno > 0)) || continue
		# get authors, most frequent contributor first
		git log $first_commit..HEAD --pretty=tformat:%an "$file" | sort | uniq -c | sort -rn \
		| while IFS=' ' read -r throwaway_number name
		do
			fix_authorname name
			aline="$name <${email[$name]}>"
			centre_line aline
			# if not already in file, add to sed script
			if ! grep -q "^[$cc]$aline[$cc]$" "$file"; then
				sedscript=${sedscript:-"$alineno { "}$'a\\\n'${cc}${aline}${cc}$'\n'
			fi
		done
		[[ -n $sedscript ]] && sedscript+='}'
	fi
	# add sed commands to update copyright line
	sedscript+=$'\n'"/(c) [0-9]\{4\}-[0-9]\{4\} Contributors to ksh 93u+m/ s/-[0-9]\{4\}/-$current_year/"
	sedscript+=$'\n'"/$current_year/ ! s/   Copyright (c) \([0-9]\{4\}\) Contributors to ksh 93u+m  /Copyright (c) \1-$current_year Contributors to ksh 93u+m/"
	# update the file if the sed script makes a difference
	sed "$sedscript" $file >|$tmpfile || exit
	update_file $file
done
Clone this wiki locally