#! /bin/bash # Copyright 2017 Sébastien Bigaret # 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__HEAD'can be deleted.