mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 20:03:05 +02:00
287af3fc3d
I mistankely put this tool in the root directory. Also, it lost its executable bit, so restoring it.
407 lines
12 KiB
Bash
Executable File
407 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# This script is meant to be used to integrate changes from a release or hotfix
|
|
# branch into the develop branch.
|
|
#
|
|
# Use:
|
|
# gitflow-integrate
|
|
# to see how to use the tool.
|
|
|
|
# Add this directory to the path.
|
|
PATH=$(dirname $0)/lib:$PATH
|
|
. bash-util.sh || exit 1
|
|
. git-util.sh || exit 1
|
|
|
|
|
|
# Executes the given command, discarding any output to stderr.
|
|
#
|
|
# arguments:
|
|
# the command to execute
|
|
#
|
|
# stdout:
|
|
# the output of the command
|
|
#
|
|
# return:
|
|
# the return value of the command
|
|
function quietly() {
|
|
"$@" 2>/dev/null
|
|
}
|
|
|
|
|
|
# Executes the given command, discarding any output to stdout or stderr.
|
|
#
|
|
# arguments:
|
|
# the command to execute
|
|
#
|
|
# return:
|
|
# the return value of the command
|
|
function silently() {
|
|
"$@" 2>/dev/null >/dev/null
|
|
}
|
|
|
|
|
|
# Outputs an error message and exits the script.
|
|
#
|
|
# arguments:
|
|
# message: one or more strings, the message to be printed
|
|
#
|
|
# stderr:
|
|
# the message
|
|
function fatal() {
|
|
echo "FATAL: $*" 1>&2
|
|
exit 113 # Use a special error code so that we can identify fatal failures.
|
|
}
|
|
|
|
|
|
# Output an error message for a missing argument and terminates with a fatal
|
|
# error.
|
|
#
|
|
# arguments:
|
|
# name: the name of the missing argument
|
|
#
|
|
# stderr:
|
|
# an error message for the missing argument (to stderr)
|
|
function missing_arg() {
|
|
fatal "Missing argument: $1"
|
|
}
|
|
|
|
|
|
# Output an error message if given any arguments and (in that case) also
|
|
# terminates with a fatal error.
|
|
#
|
|
# arguments:
|
|
# arguments: the remaining arguments to the function
|
|
function no_more_args() {
|
|
test $# = 0 || fatal "Too many arguments: $*"
|
|
}
|
|
|
|
|
|
# Outputs the body of the commit message for a given commit.
|
|
#
|
|
# arguments:
|
|
# commit: the SHA of the commit
|
|
#
|
|
# stdout:
|
|
# the body of the commit message
|
|
function git_commit_body() {
|
|
local commit="$1"; shift || missing_arg commit
|
|
no_more_args "$@"
|
|
|
|
git log -n1 --format="%B" "$commit" | cat
|
|
}
|
|
|
|
|
|
# Outputs the SHAs of the commits up to the given one that are not yet
|
|
# integrated into the base commit.
|
|
#
|
|
# arguments:
|
|
# commit: the SHA of the commit to integrate
|
|
# base: the SHA of the base commit to integrate into
|
|
#
|
|
# Outputs:
|
|
# the abbreviated SHAs of all commits that still need to be integrated
|
|
function git_commits_not_in() {
|
|
local commit="$1"; shift || missing_arg commit
|
|
local base="$1"; shift || missing_arg base
|
|
no_more_args "$@"
|
|
|
|
git log --format='%h' --first-parent $base..$commit
|
|
}
|
|
|
|
|
|
# Outputs the abbreviated SHA for the given commit.
|
|
#
|
|
# arguments:
|
|
# commit: the SHA of a commit
|
|
#
|
|
# stdout:
|
|
# the abbreviated SHA of the commit
|
|
function git_abbrev() {
|
|
local commit="$1"; shift
|
|
test $# = 0
|
|
|
|
git log -n1 --format=%h "$commit"
|
|
}
|
|
|
|
|
|
# Commits the current merge using a specified message.
|
|
#
|
|
# arguments:
|
|
# passed to 'git commit' itself
|
|
#
|
|
# stdin:
|
|
# used as the commit message
|
|
function git_commit_merge_with_message() {
|
|
# Copy the message to the merge message file.
|
|
cat - >"$(git rev-parse --git-dir)/MERGE_MSG"
|
|
# And commit the changes without invoking the editor.
|
|
EDITOR=true git commit "$@"
|
|
}
|
|
|
|
|
|
function git_is_branch() {
|
|
local branch="$1"; shift || missing_arg branch
|
|
no_more_args "$@"
|
|
|
|
git for-each-ref --format='%(refname:short)' refs/heads/ |
|
|
silently grep "^$branch\$"
|
|
}
|
|
|
|
# Prints the message that should be used to integrate a given commit.
|
|
#
|
|
# The message can have an optional prefix put in front of it.
|
|
#
|
|
# If the commit is a merge, all changes that were not included in the base
|
|
# commit are added to the message.
|
|
#
|
|
# arguments:
|
|
# prefix: a prefix to be added to the message
|
|
# commit: the SHA of the commit to be integrated
|
|
# base: the SHA of the base commit into which to integrate
|
|
#
|
|
# stdout:
|
|
# the message to used to integrate commit into base
|
|
function message() {
|
|
local prefix="$1"; shift || missing_arg prefix
|
|
local commit="$1"; shift || missing_arg commit
|
|
local base="$1"; shift || missing_arg base
|
|
no_more_args "$@"
|
|
|
|
# Determines if this is a merge that we want to follow.
|
|
if git_commit_body "$commit" | silently grep "^Merge pull request #"; then
|
|
# This is merging a pull request: get the message from the second parent.
|
|
message "$prefix" "$commit^2" "$base"
|
|
elif git_commit_body "$commit" | silently grep "^Merge commit '"; then
|
|
# This is merging some other commit: get the message from the second parent.
|
|
message "$prefix" "$commit^2" "$base"
|
|
elif git_commit_body "$commit" | silently grep "^Merge branch '"; then
|
|
# This is merging some other branch: get the message from the second parent.
|
|
message "$prefix" "$commit^2" "$base"
|
|
else
|
|
if [ $(git_commits_not_in "$commit" "$base" | wc -l) = 1 ]; then
|
|
# If there is only one commit to integrate, get its message body.
|
|
echo -n "$prefix"
|
|
git_commit_body "$commit"
|
|
else
|
|
# Otherwise we are actually integrating more than one commit here.
|
|
|
|
# Find all the commits being integrated in reverse order.
|
|
integrated_commits="$(git_commits_not_in "$commit" "$base" | tac)"
|
|
|
|
# Construct the subject line using those as a comma-separated list.
|
|
echo "$prefix${integrated_commits//$'\n'/,}"
|
|
|
|
echo # Add an empty line separating the body.
|
|
|
|
# Build the body using the commit messages of the integrated commits
|
|
# (still in reverse order).
|
|
for integrated_commit in $integrated_commits; do
|
|
echo "Commit '$integrated_commit'"
|
|
git_commit_body "$integrated_commit"
|
|
done
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
# Returns true if the message associated with integrating a commit contains a
|
|
# given string.
|
|
#
|
|
# arguments:
|
|
# string: to be looked for
|
|
# commit: the SHA of the commit to be integrated
|
|
# base: the SHA of the base commit into which to do the integrate
|
|
#
|
|
# return:
|
|
# true if the string was contained in the message
|
|
function message_contains() {
|
|
local string="$1"; shift || missing_arg string
|
|
local commit="$1"; shift || missing_arg commit
|
|
local base="$1"; shift || missing_arg base
|
|
no_more_args "$@"
|
|
|
|
message "" "$commit" "$base" | silently grep "$string"
|
|
}
|
|
|
|
|
|
# Determine whether a particular commit should be skipped when being integrated
|
|
# in the given base.
|
|
#
|
|
# arguments:
|
|
# commit: the SHA of the commit to be integrated
|
|
# base: the SHA into which the commit is to be integrated
|
|
# force_skip: set to 1 if the commit should be skipped
|
|
# force_merge: set to 1 if the commit should be merged
|
|
#
|
|
# return:
|
|
# zero if it should be skipped, non-zero otherwise
|
|
function should_skip() {
|
|
local commit="$1"; shift || missing_arg commit
|
|
local base="$1"; shift || missing_arg base
|
|
local force_skip="$1"; shift || missing_arg force_skip
|
|
local force_merge="$1"; shift || missing_arg force_merge
|
|
no_more_args "$@"
|
|
|
|
if [ "$force_skip" = 1 ]; then
|
|
# Must skip.
|
|
return 0
|
|
elif [ "$force_merge" = 1 ]; then
|
|
# Must merge.
|
|
return 1
|
|
elif message_contains "^@branch-specific$" "$commit" "$base"; then
|
|
# Skip anything marged with the @branch-specific tag.
|
|
return 0
|
|
else
|
|
# No reason to skip, then merge.
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
|
|
# Integrates a single change into the given branch.
|
|
#
|
|
# It automatically determines if the commit should be skipped.
|
|
#
|
|
# If either force_skip or force_merge are set to 1, then the logic to
|
|
# automatically determine if the commit should be skipped or merged is ignored.
|
|
#
|
|
# arguments:
|
|
# commit: the SHA of the commit to be integrated
|
|
# from_branch: the name of the branch the commit comes from
|
|
# branch: the name of the branch into which the commit should be integrated
|
|
# force_skip: set to 1 if the commit should be skipped
|
|
# force_merge: set to 1 if the commit should be merged
|
|
function integrate() {
|
|
local commit="$1"; shift || missing_arg commit
|
|
local from_branch="$1"; shift || missing_arg from_branch
|
|
local branch="$1"; shift || missing_arg branch
|
|
local force_skip="$1"; shift || missing_arg force_skip
|
|
local force_merge="$1"; shift || missing_arg force_merge
|
|
no_more_args "$@"
|
|
|
|
echo "Merging '$commit'"
|
|
|
|
local base="$(git_abbrev "$branch")"
|
|
if should_skip "$commit" "$base" "$force_skip" "$force_merge"; then
|
|
# We should skip this change: we create a merge commit, but ignore any
|
|
# change that it would bring in, and make an empty commit for it.
|
|
git merge "$commit" --no-ff --no-commit -s ours
|
|
# Finalize the commit by specifying the right message.
|
|
local prefix="Skipped '$(git_abbrev "$commit")' from $from_branch: "
|
|
message "$prefix" "$commit" "$base" |
|
|
git_commit_merge_with_message --allow-empty
|
|
# Just to be sure, check that there are no changes in the newly commited
|
|
# change.
|
|
if [ $(git diff HEAD^ | wc -l) != 0 ]; then
|
|
echo "WARNING: Skipped change actually had changes."
|
|
fi
|
|
else
|
|
# Otherwise, we should merge the changes from this commit.
|
|
git merge "$commit" --no-ff --no-commit || (
|
|
# If there are any conflicts, ask the user to resolve them.
|
|
echo "Please resolve conflict and press ENTER"
|
|
read
|
|
)
|
|
local prefix="Merged '$(git_abbrev "$commit")' from $from_branch: "
|
|
message "$prefix" "$commit" "$base" |
|
|
git_commit_merge_with_message
|
|
fi
|
|
}
|
|
|
|
|
|
# Show the commit message that would be associated with integrating a commit
|
|
# into a branch.
|
|
#
|
|
# args:
|
|
# commit: the SHA of the commit to be integrated
|
|
# from_branch: the name of the branch the commit comes from
|
|
# branch: the name of the branch into which the commit should be integrated
|
|
# force_skip: set to 1 if the commit should be skipped
|
|
# force_merge: set to 1 if the commit should be merged
|
|
#
|
|
# stdout:
|
|
# the message to be associated with the specified commit being integrated
|
|
function show() {
|
|
local commit="$1"; shift || missing_arg commit
|
|
local from_branch="$1"; shift || missing_arg from_branch
|
|
local branch="$1"; shift || missing_arg branch
|
|
local force_skip="$1"; shift || missing_arg force_skip
|
|
local force_merge="$1"; shift || missing_arg force_merge
|
|
no_more_args "$@"
|
|
|
|
local base="$(git_abbrev "$branch")"
|
|
if should_skip "$commit" "$base" "$force_skip" "$force_merge"; then
|
|
# Finalize the commit by specifying the right message.
|
|
local prefix="Skipped '$(git_abbrev "$commit")' from $from_branch: "
|
|
message "$prefix" "$commit" "$base"
|
|
else
|
|
local prefix="Merged '$(git_abbrev "$commit")' from $from_branch: "
|
|
message "$prefix" "$commit" "$base"
|
|
fi
|
|
}
|
|
|
|
|
|
# Prints a help message for the possible commands.
|
|
function show_help() {
|
|
no_more_args "$@"
|
|
|
|
prog_name="$(basename $0)"
|
|
echo "usage:"
|
|
echo
|
|
echo " $prog_name apply <from> <into>"
|
|
echo " Integrates a single commit in <from> to <into>."
|
|
echo " Automatically determine whether to skip or merge."
|
|
echo
|
|
echo " $prog_name skip <from> <into>"
|
|
echo " Integrates by skipping a single commit in <from> to <into>."
|
|
echo
|
|
echo " $prog_name merge <from> <into>"
|
|
echo " Integrates by merging a single commit in <from> to <into>."
|
|
echo
|
|
echo " $prog_name show <from> <into>"
|
|
echo " Shows the message associated with integrating a single commit"
|
|
echo " in <from> to <into>."
|
|
}
|
|
|
|
|
|
# This is the main body of the script.
|
|
function main() {
|
|
if [ $# = 0 ]; then
|
|
no_more_args "$@"
|
|
show_help
|
|
return 0
|
|
fi
|
|
|
|
local command="$1"; shift || missing_arg command
|
|
local from="$1"; shift || missing_arg from
|
|
local into="$1"; shift || missing_arg into
|
|
no_more_args "$@"
|
|
|
|
git_is_branch "$from" || fatal "Invalid branch: $from"
|
|
git_is_branch "$into" || fatal "Invalid branch: $into"
|
|
|
|
# Find the first commit to be integrated, i.e., the oldest one.
|
|
first_commit=$(git_commits_not_in "$from" "$into" | tail -1)
|
|
if [ "$first_commit" = "" ]; then
|
|
echo "Nothing to integrate."
|
|
return 1
|
|
fi
|
|
|
|
if [ "$command" = "apply" ]; then
|
|
integrate "$first_commit" "$from" "$into" "0" "0"
|
|
elif [ "$command" = "skip" ]; then
|
|
integrate "$first_commit" "$from" "$into" "1" "0"
|
|
elif [ "$command" = "merge" ]; then
|
|
integrate "$first_commit" "$from" "$into" "0" "1"
|
|
elif [ "$command" = "show" ]; then
|
|
show "$first_commit" "$from" "$into" "0" "0"
|
|
else
|
|
echo "Unknown command: $command"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Fail immediate if any command fails.
|
|
set -e -o pipefail
|
|
main "$@"
|