Skip to content

Instantly share code, notes, and snippets.

@dekarrin
Last active August 29, 2015 13:56
Show Gist options
  • Save dekarrin/8887229 to your computer and use it in GitHub Desktop.
Save dekarrin/8887229 to your computer and use it in GitHub Desktop.
Creates incremental patches containing only the desired changes.
#!/bin/bash
cd ~
apply ()
{
cd "$1"
files=$(echo *)
for f in $files
do
append=
if [ "$2" ]
then
append="$2/"
fi
if [ -f "$f" -a "$(echo $(basename $f) | rev | cut -d '.' -f1 | rev)" = 'patch' ]
then
echo "$append$1/$f"
elif [ -d "$f" ]
then
apply "$f" "$append$1"
fi
done
cd ..
}
while read file
do
patch -d silvermine -p 1 < $file
done <<< "$(apply patches)"
#!/bin/bash
# Below are potential script params; specific to project
DIR_WORKING=~
DIR_OLDFILE_ROOT=silvermine
DIR_NEWFILE_ROOT=ready
PATCH_PATTERN_SOURCE=features.txt
PATCH_START_MARK="DEKKY MOD START"
PATCH_END_MARK="DEKKY MOD END"
# Below are constants, NOT params
DIR_TEMP=tmpchanges # Relative to working dir
DIR_PATCHES=patches # Output patches to this dir
# Below this, constants should be fixed
SUFFIX_PATCH=.patch
SUFFIX_PROGRESS_OLD=.old
SUFFIX_PROGRESS_NEW=.new
SUFFIX_PROGRESS_PATCH=${SUFFIX_PATCH}.gen
SUFFIX_PROGRESS_REMOVAL=${SUFFIX_PROGRESS_PATCH}.rem
SUFFIX_PROGRESS_FINAL=${SUFFIX_PROGRESS_NEW}.patched
SUFFIX_PATCH_ERR=.others
# get hunk header info
#
# $1 - hunk header
# $2 - name of array to put info into
get_hunk_info ()
{
local get_hunk_info_var=$(echo $1 | sed 's/ +/:/' | sed s/[^0-9,:]//g)
echo $get_hunk_info_var | cut -d : -f1 | cut -d , -f1
echo $get_hunk_info_var | cut -d : -f1 | cut -d , -f2
echo $get_hunk_info_var | cut -d : -f2 | cut -d , -f1
echo $get_hunk_info_var | cut -d : -f2 | cut -d , -f2
}
# checks if the first hunk in a given file is wanted in the final output
#
# $1 - file to check
# $2 - pattern for mod starts that mark desired hunks
# $3 - filename to write unwanted hunk section headers to. can be omitted for none
# $4 - What to put before each line writen to $3. Only applicable if $3 is used.
#
# Returns
# '1' - hunk is wanted in final output
# '0' - hunk is not wanted in final output
# '-1' - hunk is wanted, but is mixed with additional hunks.
hunk_wanted ()
{
exit_status=0
echo_status=0
bad_lines=
while read -r line
do
if [ "$(echo $line | grep -i "$PATCH_START_MARK")" ]
then
if [ "$(echo $line | grep "$2")" ]
then
echo_status=1
else
exit_status=1
bad_lines="$bad_lines
$4$line"
fi
fi
done < "$1"
echo $echo_status
if [ $echo_status = 1 -a $exit_status = 1 -a -n "$3" ]
then
echo "$bad_lines" >> "$3"
fi
return $exit_status
}
lpad ()
{
orig=$1
len=$2
printf "%"$len"s" $orig
}
# $1 - status progress message
# $2 - rest of the message
pad_out ()
{
desired=80
have=$(expr ${#2} + 1)
needed=$(expr $desired - $have)
printf "%-"$needed"s%s" "$1" " $2"
}
# Outputs without a newline and overwrites anything that was already on the last line of
# the terminal
overwrite ()
{
output="$1"
if [ -z "$output" ]
then
read output
fi
printf "\r"
if [ -n "$LAST_OVERWRITE_LENGTH" ]
then
if [ "$LAST_OVERWRITE_LENGTH" -gt 0 ]
then
clear_space_count=0
while [ $clear_space_count -lt $LAST_OVERWRITE_LENGTH ]
do
printf " "
let clear_space_count++
done
printf "\r"
fi
fi
# only save length of last line
while read -r line
do
LAST_OVERWRITE_LENGTH=${#line}
printf "$line"
done <<< "$output"
}
# Outputs with a newline and overwrites anything that was already on the last line of
# the terminal
overwriteln ()
{
overwrite "$@"
printf "\n"
LAST_OVERWRITE_LENGTH=0
}
cd "$DIR_WORKING"
rm -rf "$DIR_TEMP" "$DIR_PATCHES"
#prepare files by patching out all changes but what we want
file_pattern=$(head -n 1 "$PATCH_PATTERN_SOURCE" | sed 's/, /\\|/g')
all_files=$(grep -lR "$file_pattern" "$DIR_NEWFILE_ROOT"/*)
total_files=$(echo "$all_files" | wc -l)
current_file=0
for file in $all_files
do
current_file=$(expr $current_file + 1)
base=$(echo "$file" | sed s@^"$DIR_NEWFILE_ROOT"\/@@)
status_dir_progress="[$current_file/$total_files] '$base':"
overwrite <<< "$(pad_out "$status_dir_progress" "scanning...")"
if [ ! -f "$DIR_OLDFILE_ROOT/$base" ]
then
overwriteln <<< "$(pad_out "$status_dir_progress" "New file; add manually")"
continue
fi
mkdir -p "$DIR_TEMP/$(dirname $base)"
temp_base=$DIR_TEMP/$base
temp_old=$temp_base$SUFFIX_PROGRESS_OLD
temp_new=$temp_base$SUFFIX_PROGRESS_NEW
temp_patch=$temp_base$SUFFIX_PROGRESS_PATCH
temp_removal=$temp_base$SUFFIX_PROGRESS_REMOVAL
temp_final=$temp_base$SUFFIX_PROGRESS_FINAL
patches_err=$DIR_PATCHES/$base$SUFFIX_PATCH_ERR
temp_err=$temp_base$SUFFIX_PATCH_ERR
cp "$file" "$temp_base"
dos2unix -q "$temp_base"
total_hunks=$(diff -u "$temp_base" "$DIR_OLDFILE_ROOT/$base" | grep '^@@ -[0-9]\+,[0-9]\+ +[0-9]\+,[0-9]\+ @@$' | wc -l)
current_hunk=0
kept_hunks=0
new_file_start=1
old_file_start=1
rm -f "$temp_final"
while true
do
let current_hunk++
tail -n +$old_file_start "$DIR_OLDFILE_ROOT/$base" > "$temp_old"
tail -n +$new_file_start "$temp_base" > "$temp_new"
diff -u "$temp_new" "$temp_old" > "$temp_patch"
if [ "$(cat "$temp_patch" | wc -l)" = 0 ]
then
if [ $total_hunks = 0 ]
then
overwriteln <<< "$(pad_out "$status_dir_progress" "none found; shouldn't happen...")"
else
overwriteln <<< "$(pad_out "$status_dir_progress" "complete; kept $(lpad $kept_hunks 3) of $(lpad $total_hunks 3) hunks")"
fi
break
else
overwrite <<< "$(pad_out "$status_dir_progress" "analyzing diff hunk $current_hunk of $total_hunks...")"
fi
second_hunk_start=$(grep -n '^@@' < "$temp_patch" | cut -f1 -d: | tail -n +2 | head -n 1)
if [ "$second_hunk_start" ]
then
head -n $(expr $second_hunk_start - 1) "$temp_patch" > "$temp_removal"
else
cp "$temp_patch" "$temp_removal"
fi
hunk_header=$(tail -n +3 "$temp_removal" | head -n 1)
read orig_start orig_num mod_start mod_num <<<$(get_hunk_info "$hunk_header")
orig_hunk_length=$(expr $orig_start + $orig_num - 1)
mod_hunk_length=$(expr $mod_start + $mod_num - 1)
new_file_start=$(expr $new_file_start + $orig_hunk_length)
old_file_start=$(expr $old_file_start + $mod_hunk_length)
desired_lines=0
wanted_status=$(hunk_wanted "$temp_removal" "$file_pattern" "$temp_err" "$kept_hunks: ")
wanted_exit=$?
if [ $wanted_status = 1 ]
then
kept_hunks=$(expr $kept_hunks + 1)
if [ $wanted_exit = 1 ]
then
overwriteln "WARNING: Multiple mods present in hunk #$kept_hunks in '$base'"
fi
desired_lines=$orig_hunk_length
else
patch -s -p 1 -d "$DIR_TEMP" < "$temp_removal"
desired_lines=$mod_hunk_length
fi
head -n $desired_lines "$temp_new" | grep -iv "$PATCH_START_MARK\\|$PATCH_END_MARK" >> "$temp_final"
done
cat "$temp_new" >> "$temp_final"
mv "$temp_final" "$temp_base"
if [ "$(diff -q "$DIR_OLDFILE_ROOT/${base}" "$temp_base")" ]
then
mkdir -p "$DIR_PATCHES/$(dirname $base)"
if [ -f "$temp_err" ]
then
mv "$temp_err" "$patches_err"
fi
diff -u "$DIR_OLDFILE_ROOT/${base}" "$temp_base" > "$DIR_PATCHES/${base}.patch"
fi
done
rm -rf $DIR_TEMP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment