#! /bin/bash

# Copyright 2017 Sébastien Bigaret <sebastien.bigaret@telecom-bretagne.eu> 
# License: 3-clause BSD, cf. https://opensource.org/licenses/BSD-3-Clause

#set -x
#export LANG=C

echo "WARNING: the script does not check all things that can go wrong.

Hence: before running the script, please:

- DO BACKUP your local repository: in case something goes wrong, you MUST be able to restore it from scratch (including its .git directory)

- Make sure that it is in a clean state (uncommitted changes and untracked files can make this script fail as well).

When this is done, edit this script and remove the exit statement below.
"
#exit 1

# Adapt the following settings to your situation

# name of the upstream remote
origin=origin
# name of the current branch
branch=master

# Maximum size allowed by the remote http server or proxy for a single push
MAX_SIZE=$((90*1024*1024)) # 90Mo e.g.

# NB: to cache https credentials for 10 minutes (600 seconds), run:
#git config credential.helper 'cache --timeout=600'


# ----------------------------------------------------------------------
# fetch upstream changes, if any
git fetch ${origin}

# get the revisions existing only locally, not pushed:
readarray -t revs < <( git cherry -v redmine/${branch} | grep '^+ ' | cut -d' ' -f2 )

git branch old_${branch}_HEAD  # mark the original branch head
git reset --hard ${revs[0]}^   # back to the last revision pushed upstream

RM=/bin/rm
GIT=/usr/local/bin/git

base_rev=${branch}

function save_properties()
{
    # to correctly preserve the file's attributes, we'd better stay on the
    # same filesystem (FS): let's use the directory where the file is
    # stored, as the temp.dir. may be on a different FS,
    local file_properties=$(mktemp -p $(dirname ${1}))
    cp --attributes-only --preserve=all "${1}" "${file_properties}"
    echo "${file_properties}"
}

function restore_properties()
{
    local file_properties="$1"
    local file="$2"
    cp --attributes-only --preserve=all "${file_properties}" "${file}"
}

for rev in "${revs[@]}"; do
  # For each revision:
  # 1. try to push it upstream;
  # 2. if the push fails, break it into pieces, one file after the other
  # 3. if a file is too big for a push, split it into smaller pieces
  #commit_prefix="rev. ${rev:0:10} - add $file - " # debugging purpose
  commit_prefix=""

  rev_comment=$(git log -1 --pretty=%B ${rev})
  # apply a revision (fast-forward)
  git merge $rev
  # Push it upstream, as a whole
  if git push ${origin} ${branch}; then
      base_rev=${rev}
      continue
  else
      git reset HEAD^ # back-pedal
  fi;

  # ok, the push failed, lets do it one file after the other
  git checkout -b temporary_branch
  git cherry-pick --no-commit $rev
  # get the list of files involved in that rev.
  readarray -t files \
    < <(git diff-tree --no-commit-id --name-only -r "${base_rev}" ${rev})
  nb_files=${#files[*]}
  idx=0
  for file in "${files[@]}"; do
    idx=$((idx+1))
    file_size=$(wc -c < "${file}")
    if [ $file_size -le $MAX_SIZE ]; then
      git add $file
      git commit -m "${rev_comment} (${idx}/${nb_files})"
      git push $origin temporary_branch
    else
      # the file is too big: let's split it
      split --additional-suffix=.split -d -e -b${MAX_SIZE} "$file" "$file."

      # we take care of preserving the file attributes
      file_properties=$(save_properties "${file}")

      echo > $file # *squash*

      shopt -s nullglob
      splitted_files=("${file}".[0-9]*.split)

      idx_split=0
      for s in "${splitted_files[@]}"; do
        cat "$s" >> "${file}"
        ${RM} -f "$s"
        restore_properties "${file_properties}" "${file}"
        git add $file
        git commit -m "${commit_prefix}{rev_comment} (${idx}/${nb_files})-(${idx_split}/${#splitted_files[*]})"
        git push $origin temporary_branch
      done
      ${RM} "${file_properties}"
    fi
  done # done for ${rev}

  # merge the commit(s) back into the main branch
  if [ ${nb_files} -le 1 ]; then
    git commit --amend -m "${rev_comment}"
    git checkout "${branch}"
    git merge temporary_branch
    git push -f "${origin}" temporary_branch
  else
    git checkout "${branch}"
    git merge --no-ff -m "${rev_comment}" temporary_branch
  fi

  git push "${origin}" ${branch}
  # clean up and prepare the next iteration
  git branch -d temporary_branch
  git push "${origin}" :temporary_branch
  base_rev="${rev}"
done

# check that everything is ok.
# When you are sure, the branch 'OLD_<branch_name>_HEAD'can be deleted.
