-
Notifications
You must be signed in to change notification settings - Fork 31
/
safeMerge.sh
executable file
·225 lines (208 loc) · 6.75 KB
/
safeMerge.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/bin/bash
#
# Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
#
#
# Pushes specified revision via [Space Safe Merge](https://helpserver.labs.jb.gg/help/space/internal/branch-and-merge-restrictions.html#safe-merge).
#
# Usage:
# ./safeMerge.sh [<target_branch>] [--dry-run]
# ./safeMerge.sh [<revision>[:<target_branch>]] [--dry-run]
# <revision> is HEAD by default
# <target_branch> is currently checked out branch by default (can be only master, stable or release branch)
# '--dry-run' option will start Safe Merge in a dry run mode
declare dry_run
declare revision
declare target_branch
declare remote
declare remote_url
declare user_name
declare build_type
declare safepush_type
function isCommitHash() {
if [[ "$1" =~ ^[0-9a-f]{40}$ ]]; then
return 0
else
return 1
fi
}
function parseArguments() {
dry_run=false
revision=HEAD
build_type=''
safepush_type=''
if [[ "$#" -gt 2 ]]; then
echo >&2 "Expected two arguments at most, see 'Usage' section of safeMerge.sh script"
exit 1
fi
target_branch="$(git rev-parse --abbrev-ref HEAD)"
for arg in "$@"; do
if [[ -z "${arg// /}" ]]; then
echo >&2 "Unexpected blank argument '$arg', see 'Usage' section of safeMerge.sh script"
exit 1
elif [[ "$arg" = '--dry-run' ]]; then
dry_run=true
elif [[ "$arg" == *:* ]]; then
revision="${arg%:*}"
target_branch="${arg#*:}"
elif isCommitHash "$arg"; then
revision="$arg"
else
case "$arg" in
compile)
safepush_type=compile
build_type=SafePushWithCompilation
;;
all|test|tests)
safepush_type=test
build_type=SafePushWithTests
;;
fleet)
safepush_type=fleet
build_type=SafePushFleet
;;
*)
target_branch="$arg"
;;
esac
fi
done
git check-ref-format --branch "$target_branch"
remote="$(git config --get "branch.${target_branch}.remote" || true)"
remote="${remote:-$(git config --get remote.pushDefault || true)}"
remote="${remote:-origin}"
remote_url="$(git remote get-url --push "$remote")"
user_name="$(git config user.name || echo "${GIT_COMMITTER_NAME:-}")"
user_name="$(userName "$user_name")"
checkBuildType
}
# due to environment or repository configuration what should not affect parseArguments test
function checkEnvironment() {
if [[ -z "$user_name" ]]; then
echo >&2 "Missing user.name, please execute in terminal:"
echo >&2 "> git config user.name <your_name>"
echo >&2 "or set GIT_COMMITTER_NAME=<your_name> environment variable"
exit 1
fi
if ! ssh -V; then
echo >&2 "ssh is required to use space merge"
exit 1
fi
if ! isSshRemote "$remote_url"; then
echo >&2 "$remote_url: ssh:// is required to use space merge"
exit 1
fi
checkBuildType
}
function checkBuildType() {
if [[ -n "$safepush_type" ]]; then
echo >&2 "'$safepush_type' cannot be used together with SPACE=true"
exit 1
fi
if [[ -n "$build_type" ]]; then
echo >&2 "'$build_type' cannot be used together with SPACE=true"
exit 1
fi
}
function countLocalRevisions() {
count="$(git rev-list --count "$remote/$target_branch..$revision")"
echo "$count"
}
function isSshRemote() {
if [[ "$1" == 'ssh://'* ]]; then
return 0
else
return 1
fi
}
function userName() {
echo "$1" | tr ' ' - | tr '.' - | tr '[:upper:]' '[:lower:]'
}
function spaceRepository() {
local space_repository="${1#"ssh://"}" # everything after ssh:// prefix
space_repository="${space_repository#*/}" # everything after the first slash
echo "$space_repository"
}
function safeMergeCenterUrl() {
local project_key
local repository="${1#*/}" # everything after the first slash
repository="${repository%.*}" # everything before the first dot
if [[ "$1" == */* ]]; then
project_key="${1%/*}" # everything before the first slash
else
project_key=ij
fi
echo "https://jetbrains.team/p/$project_key/repositories/$repository/safe-merges"
}
# IDs of YouTrack issues, Exception Analyzer issues and Space reviews are parsed and joined as a push ID.
# If none are found, then random push ID is generated. This behaviour is intentionally different from IDE plugin version due to complexity of implementation in Shell.
function pushID() {
local -r delimiter='+'
local push_id
push_id=$(echo "$@" |
# IDs of YouTrack issues, Exception Analyzer issues and Space reviews are matched
{ grep --only-matching '[a-zA-Z-]\+-[0-9]\+' || true; } |
tr '[:lower:]' '[:upper:]' | sort --unique |
# everything joined with delimiter, trailing delimiter removed
tr '\n' "$delimiter" | sed "s/$delimiter$/\n/")
if [[ -z "${push_id// /}" ]]; then
# '-sh' suffix is added to distinguish from IDE pushes for statistic
push_id="$RANDOM-sh"
fi
echo "$push_id"
}
function spaceSshCommand() {
local -r space_repository="$(spaceRepository "$remote_url")"
# shellcheck disable=SC2029
(set -x; ssh git.jetbrains.team space "$space_repository" "$@")
}
function safeMerge() {
local -r commit_messages="$(git log --format=%B "$remote/$target_branch..$revision")"
local -r push_id="$(pushID $commit_messages)"
local -r branch="refs/safemerge/$user_name/$push_id/$target_branch"
push "$branch"
if ! spaceSshCommand mr-find "$branch"; then
spaceSshCommand mr-create "$branch" "$target_branch"
fi
# will fail if safe merge isn't running yet
spaceSshCommand mr-cancel "$branch" || true
if [[ "$dry_run" = true ]]; then
# may timeout, see CRL-T-25610
spaceSshCommand mr-dry-run "$branch" || true
else
# may timeout, see CRL-T-25610
spaceSshCommand mr-rebase "$branch" --delete-source-branch || true
fi
local -r space_repository="$(spaceRepository "$remote_url")"
safeMergeCenterUrl="$(safeMergeCenterUrl "$space_repository")"
echo "Track all your Safe-Pushes here $safeMergeCenterUrl"
}
function push() {
local -r branch="$1"
git check-ref-format --branch "$branch"
git push --quiet "$remote" "+$revision:$branch"
}
function pull() {
# unstashing cannot restore Changelists, please upvote https://youtrack.jetbrains.com/issue/IDEA-62426
git status -uno --porcelain | grep "^\(A.\| M\|D.\| D\)\s" -q && {
git stash
trap 'git stash pop' EXIT
}
local -r ref_spec="refs/heads/$target_branch:refs/remotes/$remote/$target_branch"
git pull --rebase "$remote" "$ref_spec" || echo >&2 "Failed to perform git pull"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
set -euo pipefail
cd "$(dirname "$0")"
if ! git --version; then
echo >&2 "git is missing, push cannot be performed"
exit 1
fi
parseArguments "$@"
if [[ "$(countLocalRevisions)" = 0 ]]; then
echo "Nothing to push"
exit 0
fi
checkEnvironment
safeMerge
fi