mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-19 19:42:17 +02:00
414 lines
12 KiB
Bash
Executable File
414 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"
|
|
|
|
local current_branch=$(git branch | grep \* | cut -d ' ' -f2)
|
|
|
|
if [ "$current_branch" != "$into" ]; then
|
|
echo "Not on branch: $into"
|
|
return 1
|
|
fi
|
|
|
|
# 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 "$@"
|