| #!/bin/bash |
| # SPDX-License-Identifier: GPL-2.0 |
| |
| # 2 args: |
| # libxfs-apply <repo> <commit ID or patchfile> |
| |
| usage() |
| { |
| echo $* |
| echo |
| echo "Usage:" |
| echo " libxfs-apply [--verbose] --sob <name/email> --source <repodir> --commit <commit_id>" |
| echo " libxfs-apply --patch <patchfile>" |
| echo |
| echo "libxfs-apply should be run in the destination git repository." |
| exit |
| } |
| |
| cleanup() |
| { |
| rm -f $PATCH |
| } |
| |
| # output to stderr so it is not caught by file redirects |
| fail() |
| { |
| >&2 echo "Fail:" |
| >&2 echo $* |
| cleanup |
| exit |
| } |
| |
| # filterdiff didn't start handling git diff metadata correctly until some time |
| # after 0.3.4. The handling in 0.3.4 was buggy and broken, requiring working |
| # around that bugs to use it. Now that 0.4.2 has fixed all those bugs, the |
| # work-arounds for 0.3.4 do not work. Hence set 0.4.2 as the minimum required |
| # version and tell the user to upgrade if an old version is detected. We need to |
| # check against x.y.z version numbers here. |
| _version=`filterdiff --version | cut -d " " -f 5` |
| _major=`echo $_version | cut -d "." -f 1` |
| _minor=`echo $_version | cut -d "." -f 2` |
| _patch=`echo $_version | cut -d "." -f 3` |
| if [ $_major -eq 0 ]; then |
| if [ $_minor -lt 4 ]; then |
| fail "filterdiff $_version found. 0.4.2 or greater is required." |
| fi |
| if [ $_minor -eq 4 -a $_patch -lt 2 ]; then |
| fail "filterdiff $_version found. 0.4.2 or greater is required." |
| fi |
| fi |
| |
| # We should see repository contents we recognise, both at the source and |
| # destination. Kernel repositorys will have fs/xfs/libxfs, and xfsprogs |
| # repositories will have libxcmd. |
| SOURCE="kernel" |
| check_repo() |
| { |
| if [ ! -d "fs/xfs/libxfs" -a ! -d "libxcmd" ]; then |
| usage "$1 repository contents not recognised!" |
| fi |
| if [ -d "$REPO/libxcmd" ]; then |
| SOURCE="xfsprogs" |
| fi |
| } |
| |
| REPO= |
| PATCH= |
| COMMIT_ID= |
| VERBOSE= |
| GUILT=0 |
| STGIT=0 |
| |
| while [ $# -gt 0 ]; do |
| case "$1" in |
| --source) REPO=$2 ; shift ;; |
| --patch) PATCH=$2; shift ;; |
| --commit) COMMIT_ID=$2 ; shift ;; |
| --sob) SIGNED_OFF_BY=$2 ; shift ;; |
| --verbose) VERBOSE=true ;; |
| *) usage ;; |
| esac |
| shift |
| done |
| |
| if [ -n "$PATCH" ]; then |
| if [ -n "$REPO" -o -n "$COMMIT_ID" ]; then |
| usage "Need to specify either patch or source repo/commit" |
| fi |
| VERBOSE=true |
| elif [ -z "$REPO" -o -z "$COMMIT_ID" ]; then |
| usage "Need to specify both source repo and commit id" |
| fi |
| |
| check_repo Destination |
| |
| # Are we using guilt? This works even if no patch is applied. |
| guilt top &> /dev/null |
| if [ $? -eq 0 ]; then |
| GUILT=1 |
| fi |
| |
| # Are we using stgit? This works even if no patch is applied. |
| stg top &> /dev/null |
| if [ $? -eq 0 ]; then |
| STGIT=1 |
| fi |
| |
| #this is pulled from the guilt code to handle commit ids sanely. |
| # usage: munge_hash_range <hash range> |
| # |
| # this means: |
| # <hash> - one commit |
| # <hash>.. - hash until head (excludes hash, includes head) |
| # ..<hash> - until hash (includes hash) |
| # <hash1>..<hash2> - from hash to hash (inclusive) |
| # |
| # The output of this function is suitable to be passed to "git rev-list" |
| munge_hash_range() |
| { |
| case "$1" in |
| *..*..*|*\ *) |
| # double .. or space is illegal |
| return 1;; |
| ..*) |
| # e.g., "..v0.10" |
| echo ${1#..};; |
| *..) |
| # e.g., "v0.19.." |
| echo ${1%..}..HEAD;; |
| *..*) |
| # e.g., "v0.19-rc1..v0.19" |
| echo ${1%%..*}..${1#*..};; |
| ?*) |
| # e.g., "v0.19" |
| echo $1^..$1;; |
| *) # empty |
| return 1;; |
| esac |
| return 0 |
| } |
| |
| # Filter the patch into the right format & files for the other tree |
| filter_kernel_patch() |
| { |
| local _patch=$1 |
| local _libxfs_files="" |
| |
| # The files we will try to apply to |
| _libxfs_files=`mktemp` |
| ls -1 fs/xfs/libxfs/*.[ch] | sed -e "s%.*/\(.*\)%*\1%" > $_libxfs_files |
| |
| # Create the new patch |
| # filterdiff will have screwed up files that source/sink /dev/null. |
| # fix that up with some sed magic. |
| filterdiff \ |
| --verbose \ |
| -I $_libxfs_files \ |
| --strip=1 \ |
| --addoldprefix=a/fs/xfs/ \ |
| --addnewprefix=b/fs/xfs/ \ |
| $_patch | \ |
| sed -e 's, [ab]\/fs\/xfs\/\(\/dev\/null\), \1,' |
| |
| |
| rm -f $_libxfs_files |
| } |
| |
| filter_xfsprogs_patch() |
| { |
| local _patch=$1 |
| local _libxfs_files="" |
| |
| # The files we will try to apply to. We need to pull this from the |
| # patch, as the may be libxfs files added in this patch and so we |
| # need to capture them. |
| _libxfs_files=`mktemp` |
| #ls -1 libxfs/*.[ch] | sed -e "s%.*/\(.*\)%*libxfs/\1%" > $_libxfs_files |
| lsdiff $_patch | sed -e "s%.*/\(.*\)%*libxfs/\1%" > $_libxfs_files |
| |
| # Create the new patch |
| # filterdiff will have screwed up files that source/sink /dev/null. |
| # fix that up with some sed magic. |
| filterdiff \ |
| --verbose \ |
| -I $_libxfs_files \ |
| --strip=3 \ |
| --addoldprefix=a/ \ |
| --addnewprefix=b/ \ |
| $_patch | \ |
| sed -e 's, [ab]\/\(\/dev\/null\), \1,' |
| |
| rm -f $_libxfs_files |
| } |
| |
| add_header() |
| { |
| local hdr="$1" |
| local hdrfile="$2" |
| |
| tail -n 1 "$hdrfile" | grep -q "^${hdr}$" || echo "$hdr" >> "$hdrfile" |
| } |
| |
| fixup_header_format() |
| { |
| local _source=$1 |
| local _patch=$2 |
| local _hdr=`mktemp` |
| local _diff=`mktemp` |
| local _new_hdr=$_hdr.new |
| |
| # Split the header on the first ^diff --git line (convenient!) |
| sed -e /^diff/q $_patch > $_hdr |
| cat $_patch | awk ' |
| BEGIN { diff_seen = 0; index_seen = 0 } |
| /^diff/ { diff_seen++; next } |
| /^index/ { if (++index_seen == 1) { next } } |
| // { if (diff_seen) { print $0 } }' > $_diff |
| |
| # the header now has the format: |
| # commit 0d5a75e9e23ee39cd0d8a167393dcedb4f0f47b2 |
| # Author: Eric Sandeen <sandeen@sandeen.net> |
| # Date: Wed Jun 1 17:38:15 2016 +1000 |
| # |
| # xfs: make several functions static |
| #.... |
| # Signed-off-by: Dave Chinner <david@fromorbit.com> |
| # |
| # We want to format it like a normal patch with a line to say what repo |
| # and commit it was sourced from: |
| # |
| # xfs: make several functions static |
| # |
| # From: Eric Sandeen <sandeen@sandeen.net> |
| # |
| # Source kernel commit: 0d5a75e9e23ee39cd0d8a167393dcedb4f0f47b2 |
| # |
| # <body> |
| # |
| # To do this, use sed to first strip whitespace, then pass it into awk |
| # to rearrange the headers. |
| sed -e 's/^ *//' $_hdr | awk -v src=$_source ' |
| BEGIN { |
| date_seen=0 |
| subject_seen=0 |
| } |
| /^commit/ { |
| commit=$2 |
| next; |
| } |
| /^Author:/ { |
| split($0, a, ":") |
| from=a[2] |
| next; |
| } |
| /^Date:/ { date_seen=1; next } |
| /^difflib/ { next } |
| |
| // { |
| if (date_seen == 0) |
| next; |
| if (subject_seen == 0) { |
| if (length($0) != 0) { |
| subject_seen=1 |
| subject=$0; |
| } |
| next; |
| } |
| if (subject_seen == 1) { |
| print subject |
| print |
| print "From:" from |
| print |
| print "Source " src " commit: " commit |
| subject_seen=2 |
| } |
| print $0 |
| }' > $_hdr.new |
| |
| # Remove the last line if it contains only whitespace |
| sed -i '${/^[[:space:]]*$/d;}' $_hdr.new |
| |
| # Add Signed-off-by: header if specified |
| if [ ! -z ${SIGNED_OFF_BY+x} ]; then |
| add_header "Signed-off-by: $SIGNED_OFF_BY" $_hdr.new |
| else # get it from git config if present |
| SOB_NAME=`git config --get user.name` |
| SOB_EMAIL=`git config --get user.email` |
| if [ ! -z ${SOB_NAME+x} ]; then |
| add_header "Signed-off-by: $SOB_NAME <$SOB_EMAIL>" $_hdr.new |
| fi |
| fi |
| |
| # now output the new patch |
| cat $_hdr.new $_diff |
| |
| rm -f $_hdr* $_diff |
| |
| } |
| |
| apply_patch() |
| { |
| local _patch=$1 |
| local _patch_name=$2 |
| local _current_commit=$3 |
| local _new_patch=`mktemp` |
| local _source="kernel" |
| local _target="xfsprogs" |
| |
| # filter just the libxfs parts of the patch |
| if [ $SOURCE == "xfsprogs" ]; then |
| |
| [ -n "$VERBOSE" ] || lsdiff $_patch | grep -q "[ab]/libxfs/" |
| if [ $? -ne 0 ]; then |
| echo "Doesn't look like an xfsprogs patch with libxfs changes" |
| echo "Skipping commit $_current_commit" |
| return |
| fi |
| |
| filter_kernel_patch $_patch > $_new_patch |
| _source="xfsprogs" |
| _target="kernel" |
| elif [ $SOURCE == "kernel" ]; then |
| |
| [ -n "$VERBOSE" ] || lsdiff $_patch | grep -q "[ab]/fs/xfs/libxfs/" |
| if [ $? -ne 0 ]; then |
| echo "Doesn't look like a kernel patch with libxfs changes" |
| echo "Skipping commit $_current_commit" |
| return |
| fi |
| |
| filter_xfsprogs_patch $_patch > $_new_patch |
| else |
| fail "Unknown source repo type: $SOURCE" |
| fi |
| |
| grep -q "Source $_target commit: " $_patch |
| if [ "$?" -eq "0" ]; then |
| echo "$_patch_name already synced up" |
| echo "$_skipping commit $_current_commit" |
| return |
| fi |
| |
| # now munge the header to be in the correct format. |
| fixup_header_format $_source $_new_patch > $_new_patch.2 |
| |
| if [ -n "$VERBOSE" ]; then |
| echo "Filtered patch from $REPO contains:" |
| lsdiff $_new_patch.2 |
| fi |
| |
| # Ok, now apply with guilt or patch; either may fail and require a force |
| # and/or a manual reject fixup |
| if [ $GUILT -eq 1 ]; then |
| [ -n "$VERBOSE" ] || echo "$REPO looks like a guilt directory." |
| PATCHES=`guilt applied | wc -l` |
| if [ -n "$VERBOSE" -a $PATCHES -gt 0 ]; then |
| echo -n "Top patch is: " |
| guilt top |
| fi |
| |
| guilt import -P $_patch_name $_new_patch.2 |
| guilt push |
| if [ $? -eq 0 ]; then |
| guilt refresh |
| else |
| echo "Guilt push of $_current_commit $_patch_name failed!" |
| read -r -p "Skip or Fail [s|F]? " response |
| if [ -z "$response" -o "$response" != "s" ]; then |
| echo "Force push patch, fix and refresh." |
| echo "Restart from commit $_current_commit" |
| fail "Manual cleanup required!" |
| else |
| echo "Skipping." |
| guilt delete -f $_patch_name |
| fi |
| fi |
| elif [ $STGIT -eq 1 ]; then |
| [ -n "$VERBOSE" ] || echo "$REPO looks like a stgit directory." |
| PATCHES=`stg series | wc -l` |
| if [ -n "$VERBOSE" -a $PATCHES -gt 0 ]; then |
| echo -n "Top patch is: " |
| stg top |
| fi |
| |
| stg import -n $_patch_name $_new_patch.2 |
| if [ $? -ne 0 ]; then |
| echo "stgit push failed!" |
| read -r -p "Skip or Fail [s|F]? " response |
| if [ -z "$response" -o "$response" != "s" ]; then |
| echo "Force push patch, fix and refresh." |
| echo "Restart from commit $_current_commit" |
| fail "Manual cleanup required!" |
| else |
| echo "Skipping. Manual series file cleanup needed!" |
| fi |
| fi |
| else |
| echo "Applying with git am:" |
| git am -s $_new_patch.2 |
| echo "Patch was applied in $REPO; check for rejects, etc" |
| fi |
| |
| rm -f $_new_patch* |
| } |
| |
| # name a guilt patch. Code is lifted from guilt import-commit. |
| name_patch() |
| { |
| s=`git log --no-decorate --pretty=oneline -1 $1 | cut -c 42-` |
| |
| # Try to convert the first line of the commit message to a |
| # valid patch name. |
| fname=`printf %s "$s" | \ |
| sed -e "s/&/and/g" -e "s/[ :]/_/g" -e "s,[/\\],-,g" \ |
| -e "s/['\\[{}]//g" -e 's/]//g' -e 's/\*/-/g' \ |
| -e 's/\?/-/g' -e 's/\.\.\.*/./g' -e 's/^\.//' \ |
| -e 's/\.patch$//' -e 's/\.$//' | tr A-Z a-z` |
| |
| # Try harder to make it a legal commit name by |
| # removing all but a few safe characters. |
| fname=`echo $fname|tr -d -c _a-zA-Z0-9---/\\n` |
| |
| echo $fname |
| } |
| |
| # single patch is easy. |
| if [ -z "$COMMIT_ID" ]; then |
| apply_patch $PATCH |
| cleanup |
| exit 0 |
| fi |
| |
| # switch to source repo and get individual commit IDs |
| # |
| # git rev-list gives us a list in reverse chronological order, so we need to |
| # reverse that to give us the order we require. |
| pushd $REPO > /dev/null |
| check_repo Source |
| hashr=`munge_hash_range $COMMIT_ID` |
| if [ $SOURCE == "kernel" ]; then |
| hashr="$hashr -- fs/xfs/libxfs" |
| else |
| hashr="$hashr -- libxfs" |
| fi |
| |
| # grab and echo the list of commits for confirmation |
| echo "Commits to apply:" |
| commit_list=`git rev-list $hashr | tac` |
| git log --oneline $hashr |tac |
| read -r -p "Proceed [y|N]? " response |
| if [ -z "$response" -o "$response" != "y" ]; then |
| fail "Aborted!" |
| fi |
| popd > /dev/null |
| |
| PATCH=`mktemp` |
| for commit in $commit_list; do |
| |
| # switch to source repo and pull commit into a patch file |
| pushd $REPO > /dev/null |
| git show $commit > $PATCH || usage "Bad source commit ID!" |
| patch_name=`name_patch $commit` |
| popd > /dev/null |
| |
| apply_patch $PATCH $patch_name $commit |
| done |
| |
| |
| cleanup |