#!/bin/bash # # (c) 2012 - 2016 PrydeWorX # Sven Eden, PrydeWorX - Bardowick, Germany # yamakuzure@users.sourceforge.net # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # History and Changelog: # Version Date Maintainer Change(s) # 0.0.1 2012-12-21 sed, PrydeWorX First Design. # 0.0.2 2012-12-29 sed, PrydeWorX First working version. # 0.0.3 2013-01-07 sed, PrydeWorX Force LC_ALL=C # 0.1.0 2013-01-09 sed, PrydeWorX Initial private release. # 0.1.1 2013-01-13 sed, PrydeWorX Added logging and error handling. # 0.1.2 2013-02-17 sed, PrydeWorX Cleanup on trapped signals added. # 0.1.3 2013-05-22 sed, PrydeWorX Use function descriptions like in # Gentoo eclasses. # 0.1.4 2013-06-01 sed, PrydeWorX Put functions into pwx_git_funcs.sh # for shared use with pwx_git_applier. # 0.2.0 2014-09-02 sed, PrydeWorX Use GNU enhanced getopt for command # line parsing. # 0.2.1 2015-03-07 sed, PrydeWorX Make and mandatory # positional arguments. # 0.3.0 2016-11-18 sed, PrydeWorX Added -w|--exchange option for # word/path substitutions. (elogind) # 0.3.1 2016-11-25 sed, PrydeWorX Add generated *.[.diff] files to # the targets .gitignore. # 0.3.2 2017-05-23 sed, PrydeWorX It is not fatal if the source dir has # no 'master' branch. # 0.3.3 2017-07-25 sed, PrydeWorX Do not build root file diffs, use # include the workings of # ./get_build_file_diff.sh instead. # 0.4.0 2017-08-08 sed, PrydeWorX Include meson build files in normal # file list. # Common functions PROGDIR="$(readlink -f $(dirname $0))" source ${PROGDIR}/pwx_git_funcs.sh # Version, please keep this current VERSION="0.4.0" # Global values to be filled in: SOURCE_TREE="" TAG_TO_USE="" LAST_MUTUAL_COMMIT="" OUTPUT="${PROGDIR}/patches" EXTRA_GIT_OPTS="" # The options for the git format-patch command: GIT_FP_OPTS="-1 -C --find-copies-harder -n" # Options and the corresponding help text OPT_SHORT=c:e:ho: OPT_LONG=commit:,exchange:,help,output: HELP_TEXT="Usage: $0 [OPTIONS] Reset the git tree in to the . Then search its history since the last mutual commit for any commit that touches at least one file in any subdirectory of the directory this script was called from. The root files, like Makefile.am or configure.ac, are not used by the search, but copied with the tag as a postfix if these files where changed. A diff file is created as well. OPTIONS: -c|--commit : The mutual commit to use. If this option is not used, the script looks into \"${PWX_COMMIT_FILE}\" and uses the commit noted for . -e|--exchange : Exchanges t for s when searching for commits and replaces s with t in the formatted patches. 't' is the [t]arget string. (HERE) 's' is the [s]ource string. (THERE) This option can be used more than once. Substitutions are performed in the same order as given on the command line. -h|--help Show this help and exit. -o|--output : Path to where to write the patches. The default is to write into the subdirectory 'patches' of the current directory. Notes: - The source tree is not reset and kept in detached state after the script finishes. - When the script succeeds, it adds a line to the commit - file \"${PWX_COMMIT_FILE}\" of the form: -last You can use that line for the next tag you wish to migrate. " # ========================================= # === Use getopt (GNU enhanced version) === # ========================================= # Check the version first, so we do not run into trouble getopt --test > /dev/null if [[ $? -ne 4 ]]; then echo "ERROR: getopt is not the GNU enhanced version." exit 1 fi # Store the output so we can check for errors. OPT_PARSED=$(getopt --options $OPT_SHORT --longoptions $OPT_LONG --name "$0" -- "$@") if [[ $? -ne 0 ]]; then # getopt has already complained about wrong arguments to stdout exit 2 fi # Use eval with "$OPT_PARSED" to properly handle the quoting eval set -- "$OPT_PARSED" # -------------------- # --- Handle input --- # -------------------- while true; do case "$1" in -c|--commit) LAST_MUTUAL_COMMIT="$2" shift 2 ;; -e|--exchange) add_exchange "$2" shift 2 ;; -h|--help) echo "$HELP_TEXT" exit 0 ;; -o|--output) OUTPUT="$2" shift 2 ;; --) shift break ;; *) echo "Something went mysteriously wrong." exit 3 ;; esac done # At this point we must have and left if [[ $# -ne 2 ]]; then echo "$HELP_TEXT" exit 4 fi # So these must be it: SOURCE_TREE="$1" TAG_TO_USE="$2" # ====================================================== # === The directory to analyse must exist of course. === # ====================================================== THERE="$(readlink -f "$SOURCE_TREE")" if [[ ! -d "$THERE" ]]; then echo "The directory \"$SOURCE_TREE\"" if [[ -n "$THERE" ]] \ && [[ "x$THERE" != "x$SOURCE_TREE" ]]; then echo " aka \"$THERE\"" fi echo -e "could not be found" exit 1 fi # ============================================== # === It is futile to analyze this directory === # ============================================== if [[ "x$HERE" = "x$THERE" ]]; then echo "No. Please use another directory." exit 1 fi # ======================================================= # === If no last mutual commit was given, try to load === # === it from commit list. === # ======================================================= if [[ -z "$LAST_MUTUAL_COMMIT" ]]; then if [[ -f "${PWX_COMMIT_FILE}" ]]; then LAST_MUTUAL_COMMIT="$( \ grep -s -P "^$TAG_TO_USE " $PWX_COMMIT_FILE 2>/dev/null | \ cut -d ' ' -f 2)" else echo "ERROR: ${PWX_COMMIT_FILE} does not exist." echo " Please provide a last mutual commit" echo " to use." echo -e "\n$HELP_TEXT" exit 2 fi # If the tag was not found in the commit file, # error out. Analysing the trees by this script is # almost guaranteed to go wrong. if [[ -z "$LAST_MUTUAL_COMMIT" ]] ; then echo "ERROR: $PWX_COMMIT_FILE does not contain" echo " \"$TAG_TO_USE\" Please provide a last mutual" echo " commit to use." echo -e "\n$HELP_TEXT" exit 3 fi fi echo "Last mutual commit: $LAST_MUTUAL_COMMIT" # ================================================================ # === The PWX_EXCHANGES array may consist of "t:s" strings which === # === must be split. The first part is needed to rename paths === # === for the source tree history search, the second to rename === # === those paths in the patch files back into what can be === # === used here. === # ================================================================ declare -a ex_src=() declare -a ex_tgt=() for x in "${!PWX_EXCHANGES[@]}" ; do exstr=${PWX_EXCHANGES[$x]} src=${exstr##*:} tgt=${exstr%%:*} if [[ "x$src" = "x$tgt" ]]; then echo "ERROR: The exchange string \"$exstr\" is invalid" exit 4 fi # The reason we go by using two indexed arrays instead # of associative arrays is, that the output of # ${!ex_src[@]} and ${!ex_tgt[@]} would be sorted # alphanumerical. But to be able to do chained substitutions # as defined by the order on the command line, that sorting # is counterproductive. # An argument chain like: # --exchange foobar:baz --exchange bar:foo # would result in the wrong order for the target to source # substitutions, as 'bar' would be given before 'foobar'. ex_src[$x]="$src" ex_tgt[$x]="$tgt" done # ========================================================== # === The OUTPUT directory must exist and must be empty. === # ========================================================== if [[ -n "$OUTPUT" ]]; then if [[ ! -d "$OUTPUT" ]]; then mkdir -p "$OUTPUT" || die "Can not create $OUTPUT [$?]" fi if [[ -n "$(ls "$OUTPUT"/????-*.patch 2>/dev/null)" ]]; then echo "ERROR: $OUTPUT already contains patches" exit 4 fi else echo "You have set the output directory to be" echo "an empty string. Where should I put the" echo "patches, then?" exit 5 fi # ============================================================= # === We need two file lists. === # === A) The list of root files. === # === These are not analyzed beyond the question whether === # === they differ from our version or not. If they === # === differ, we'll copy them with the used tag becoming === # === a postfix. They are further analyzed later. === # === B) All files in the used subdirectories. We need all === # === commits that change these files. === # ============================================================= echo -n "Building file lists ..." # If this is a meson+ninja build, there might be a "build" subdirectory, # that we surely do not want to get analyzed. if [[ -d ./build ]]; then rm -rf ./build fi # --- File list a) Root files declare -a root_files=( $(find ./ -mindepth 1 -maxdepth 1 -type f \ -not -name '*~' -and \ -not -name '*.diff' -and \ -not -name '*.orig' -and \ -not -name '*.bak' -and \ -not -name '*meson*' -printf "%f ") ) # --- File list b) Core files declare -a core_files=( $(find ./ -mindepth 2 -type f \ -not -name '*~' -and \ -not -name '*.diff' -and \ -not -name '*.orig' -and \ -not -name '*.bak') ) echo " done" # --- Add root meson files to the core list core_files+=( $(find ./ -mindepth 1 -maxdepth 1 -type f \ -not -name '*~' -and \ -not -name '*.diff' -and \ -not -name '*.orig' -and \ -not -name '*.bak' -and \ -name '*meson*') ) # --- Add an error log file to the list of temp files PWX_ERR_LOG="/tmp/pwx_git_getter_$$.log" add_temp "$PWX_ERR_LOG" touch $PWX_ERR_LOG || die "Unable to create $PWX_ERR_LOG" # =============================================== # === Step 1: pushd into the source directory === # === and check out the tag to use. === # =============================================== pushd $THERE 1>/dev/null 2>$PWX_ERR_LOG || die "Can't pushd into \"$THERE\"" PWX_IS_PUSHD=1 # Reset to master first, to see whether this is clean git checkout master 1>/dev/null 2>&1 git checkout $TAG_TO_USE 1>/dev/null 2>$PWX_ERR_LOG res=$? if [ 0 -ne $res ] ; then die "ERROR: tag \"$TAG_TO_USE\" is unknown [$res]" fi # ====================================== # === Step 2: Now that we are THERE, === # === copy the root files. === # ====================================== echo -n "Copying root files that differ ..." # Go through the files. for f in "${root_files[@]}" ; do if [[ -f "$THERE/$f" ]] \ && [[ -n "$(diff -dqw "$HERE/$f" "$THERE/$f")" ]] ; then cp -uf "$THERE/$f" "$HERE/${f}.$TAG_TO_USE" 2>$PWX_ERR_LOG || \ die "cp \"$THERE/$f\" \"$HERE/${f}.$TAG_TO_USE\" failed" fi done echo " done" # ========================================= # === Step 3: Build the list of commits === # ========================================= echo -n "Building commit list..." # lst_a is for the raw list of commits with time stamps lst_a=/tmp/git_list_a_$$.lst touch "$lst_a" || die "Can not create $lst_a" truncate -s 0 $lst_a add_temp "$lst_a" # lst_b is for the ordered (by timestamp) and unified list lst_b=/tmp/git_list_b_$$.lst touch "$lst_b" || die "Can not create $lst_b" truncate -s 0 $lst_b add_temp "$lst_b" for f in "${core_files[@]}" ; do # Before using that file to find log entries that match, # all target->source substitutions must be used. fsrc="$f" for i in "${!ex_tgt[@]}" ; do src="${ex_src[$i]//\//\\\/}" tgt="${ex_tgt[$i]//\//\\\/}" fsrc="${fsrc//$tgt/$src}" done # Now go looking what we've got git log ${LAST_MUTUAL_COMMIT}..HEAD $fsrc 2>/dev/null | \ grep -P "^commit\s+" | \ cut -d ' ' -f 2 >> $lst_a # Note: No PWX_ERR_LOG here. If we check a file, that ONLY # exists in the target, git log will error out. done sort -u $lst_a > $lst_b truncate -s 0 $lst_a c_cnt=$(wc -l $lst_b | cut -d ' ' -f 1) echo " $c_cnt commits found" # ============================================================== # === Step 4: Get a full list of all commits since the last === # === mutual commit. These are tac'd and then used === # === to build the final list of commits, now in the === # === correct order. === # ============================================================== echo -n "Ordering by commit time..." for c in $(git log ${LAST_MUTUAL_COMMIT}..HEAD 2>/dev/null | \ grep -P "^commit\s+" | \ cut -d ' ' -f 2 | tac) ; do grep "$c" $lst_b >> $lst_a done echo " done" # ================================================================ # === Step 5: Now that we have a lst_b file with a list of all === # === relevant commits for all files found in the === # === target, the commit patches can be build. === # ================================================================ echo -n "Creating patches ..." # To be able to apply the patches in the correct order, they need # to be numbered. However, as git will only create one patch at a # time, we have to provide the numbering by ourselves. n=0 LAST_COMMIT="" for c in $(cut -d ' ' -f 2 $lst_a) ; do n=$((n+1)) n_str="$(printf "%04d" $n)" git format-patch $GIT_FP_OPTS -o $OUTPUT --start-number=$n $c \ 1>/dev/null 2>$PWX_ERR_LOG || die "git format-patch failed on $c" # Again we have to apply the exchange substitutions, now in # reverse order. If we didn't do that, the patches would # try to modify files, that do not exist in the target. # Or worse, are different files. for i in "${!ex_src[@]}" ; do src="${ex_src[$i]//,/\\,}" tgt="${ex_tgt[$i]//,/\\,}" perl -p -i -e "s,$src,$tgt,g" $OUTPUT/${n_str}-*.patch done # Store commit so the last is known when the loop ends LAST_COMMIT="$c" done echo " done" # ============================================= # === Step 6: Get the real root files diffs === # ============================================= echo -n "Building root file diffs ..." git checkout ${LAST_MUTUAL_COMMIT} 1>/dev/null 2>$PWX_ERR_LOG res=$? if [ 0 -ne $res ] ; then die "ERROR: tag \"${LAST_MUTUAL_COMMIT}\" is unknown [$res]" fi # The work is done HERE popd 1>/dev/null 2>&1 PWX_IS_PUSHD=0 # Now go through the files: for t in "${root_files[@]}"; do f="${t}.${TAG_TO_USE}" if [[ ! -f "${f}" ]]; then continue fi case "$t" in autogen.sh|Makefile-man.am|README) rm -f "$f" ;; CODING_STYLE|NEWS|TODO|.dir-locals.el|.mailmap|.vimrc|.ymc_extra_conf.py) mv "$f" "$t" ;; .gitignore|configure.ac|Makefile.am|configure|Makefile) diff -dwu $THERE/$t "$f" | \ sed -e "s,$THERE/$t,a/$t," \ -e "s,$f,b/$t," > ${t}.diff rm -f "${f}" ;; *) echo "WHAT the hell should I do with \"$f\" -> \"$t\" ?" ;; esac done # Get upstream back THERE pushd $THERE 1>/dev/null 2>$PWX_ERR_LOG || die "Can't pushd into \"$THERE\"" PWX_IS_PUSHD=1 git checkout $TAG_TO_USE 1>/dev/null 2>&1 # ====================================================== # === Step 7: Make the last commit found to be known === # ====================================================== popd 1>/dev/null 2>&1 PWX_IS_PUSHD=0 if [[ -n "$LAST_COMMIT" ]]; then m="${TAG_TO_USE}-last" if [[ -f $PWX_COMMIT_FILE ]] \ && [[ 0 -lt $(grep -c "$m" $PWX_COMMIT_FILE) ]]; then echo "Substituting $m with $LAST_COMMIT" perl -p -i -e "s,^$m\s+\S+$,$m $LAST_COMMIT," $PWX_COMMIT_FILE else echo "Setting $m to $LAST_COMMIT" echo "$m $LAST_COMMIT" >> $PWX_COMMIT_FILE fi fi # ======================================== # === Cleanup : Remove temporary files === # ======================================== cleanup echo "All finished"