git-merge-forward.sh (10904B)
1 #!/usr/bin/env bash 2 3 SCRIPT_NAME=$(basename "$0") 4 5 function usage() 6 { 7 echo "$SCRIPT_NAME [-h] [-n] [-t <test-branch-prefix> [-u]]" 8 echo 9 echo " arguments:" 10 echo " -h: show this help text" 11 echo " -n: dry run mode" 12 echo " (default: run commands)" 13 echo " -t: test branch mode: create new branches from the commits checked" 14 echo " out in each maint directory. Call these branches prefix_035," 15 echo " prefix_040, ... , prefix_main." 16 echo " (default: merge forward maint-*, release-*, and main)" 17 echo " -u: in test branch mode, if a prefix_* branch already exists," 18 echo " skip creating that branch. Use after a merge error, to" 19 echo " restart the merge forward at the first unmerged branch." 20 echo " (default: if a prefix_* branch already exists, fail and exit)" 21 echo 22 echo " env vars:" 23 echo " required:" 24 echo " TOR_FULL_GIT_PATH: where the git repository directories reside." 25 echo " You must set this env var, we recommend \$HOME/git/" 26 echo " (default: fail if this env var is not set;" 27 echo " current: $GIT_PATH)" 28 echo 29 echo " optional:" 30 echo " TOR_MASTER: the name of the directory containing the tor.git clone" 31 echo " The primary tor git directory is \$GIT_PATH/\$TOR_MASTER" 32 echo " (default: tor; current: $TOR_MASTER_NAME)" 33 echo " TOR_WKT_NAME: the name of the directory containing the tor" 34 echo " worktrees. The tor worktrees are:" 35 echo " \$GIT_PATH/\$TOR_WKT_NAME/{maint-*,release-*}" 36 echo " (default: tor-wkt; current: $TOR_WKT_NAME)" 37 echo " we recommend that you set these env vars in your ~/.profile" 38 } 39 40 ################# 41 # Configuration # 42 ################# 43 44 # Don't change this configuration - set the env vars in your .profile 45 46 # Where are all those git repositories? 47 GIT_PATH=${TOR_FULL_GIT_PATH:-"FULL_PATH_TO_GIT_REPOSITORY_DIRECTORY"} 48 # The main branch git repository directory from which all the worktree have 49 # been created. 50 TOR_MASTER_NAME=${TOR_MASTER_NAME:-"tor"} 51 # The worktrees location (directory). 52 TOR_WKT_NAME=${TOR_WKT_NAME:-"tor-wkt"} 53 54 ########################## 55 # Git branches to manage # 56 ########################## 57 58 # The branches and worktrees need to be modified when there is a new branch, 59 # and when an old branch is no longer supported. 60 61 # Configuration of the branches that needs merging. The values are in order: 62 # (0) current maint/release branch name 63 # (1) previous maint/release name to merge into (0) 64 # (only used in merge forward mode) 65 # (2) Full path of the git worktree 66 # (3) current branch suffix 67 # (maint branches only, only used in test branch mode) 68 # (4) previous test branch suffix to merge into (3) 69 # (maint branches only, only used in test branch mode) 70 # 71 # Merge forward example: 72 # $ cd <PATH/TO/WORKTREE> (2) 73 # $ git checkout maint-0.3.5 (0) 74 # $ git pull 75 # $ git merge maint-0.3.4 (1) 76 # 77 # Test branch example: 78 # $ cd <PATH/TO/WORKTREE> (2) 79 # $ git checkout -b ticket99999_035 (3) 80 # $ git checkout maint-0.3.5 (0) 81 # $ git pull 82 # $ git checkout ticket99999_035 83 # $ git merge maint-0.3.5 84 # $ git merge ticket99999_034 (4) 85 # 86 # First set of arrays are the maint-* branch and then the release-* branch. 87 # New arrays need to be in the WORKTREE= array else they aren't considered. 88 # 89 # Only used in test branch mode 90 # We create a test branch for the earliest maint branch. 91 # But it's the earliest maint branch, so we don't merge forward into it. 92 # Since we don't merge forward into it, the second and fifth items must be 93 # blank (""). 94 95 # origin that will be used to fetch the updates. All the worktrees are created 96 # from that repository. 97 ORIGIN_PATH="$GIT_PATH/$TOR_MASTER_NAME" 98 99 ####################### 100 # Argument processing # 101 ####################### 102 103 # Controlled by the -n option. The dry run option will just output the command 104 # that would have been executed for each worktree. 105 DRY_RUN=0 106 107 # Controlled by the -t <test-branch-prefix> option. The test branch base 108 # name option makes git-merge-forward.sh create new test branches: 109 # <tbbn>_035, <tbbn>_040, ... , <tbbn>_main, and merge forward. 110 TEST_BRANCH_PREFIX= 111 112 # Controlled by the -u option. The use existing option checks for existing 113 # branches with the <test-branch-prefix>, and checks them out, rather than 114 # creating a new branch. 115 USE_EXISTING=0 116 117 while getopts "hnt:u" opt; do 118 case "$opt" in 119 h) usage 120 exit 0 121 ;; 122 n) DRY_RUN=1 123 echo " *** DRY RUN MODE ***" 124 ;; 125 t) TEST_BRANCH_PREFIX="$OPTARG" 126 echo " *** CREATING TEST BRANCHES: ${TEST_BRANCH_PREFIX}_nnn ***" 127 ;; 128 u) USE_EXISTING=1 129 echo " *** USE EXISTING TEST BRANCHES MODE ***" 130 ;; 131 *) 132 echo 133 usage 134 exit 1 135 ;; 136 esac 137 done 138 139 ########################### 140 # Git worktrees to manage # 141 ########################### 142 143 set -e 144 if [ -z "$TEST_BRANCH_PREFIX" ]; then 145 # maint/release merge mode 146 eval "$(git-list-tor-branches.sh -m)" 147 # Remove first element: we don't merge forward into it. 148 WORKTREE=( "${WORKTREE[@]:1}" ) 149 else 150 eval "$(git-list-tor-branches.sh -m -R)" 151 fi 152 set +e 153 154 COUNT=${#WORKTREE[@]} 155 156 ############# 157 # Constants # 158 ############# 159 160 # Control characters 161 CNRM=$'\x1b[0;0m' # Clear color 162 163 # Bright color 164 BGRN=$'\x1b[1;32m' 165 BBLU=$'\x1b[1;34m' 166 BRED=$'\x1b[1;31m' 167 BYEL=$'\x1b[1;33m' 168 IWTH=$'\x1b[3;37m' 169 170 # Strings for the pretty print. 171 MARKER="${BBLU}[${BGRN}+${BBLU}]${CNRM}" 172 SUCCESS="${BGRN}success${CNRM}" 173 FAILED="${BRED}failed${CNRM}" 174 175 #################### 176 # Helper functions # 177 #################### 178 179 # Validate the given returned value (error code), print success or failed. The 180 # second argument is the error output in case of failure, it is printed out. 181 # On failure, this function exits. 182 function validate_ret 183 { 184 if [ "$1" -eq 0 ]; then 185 printf "%s\\n" "$SUCCESS" 186 else 187 printf "%s\\n" "$FAILED" 188 printf " %s" "$2" 189 exit 1 190 fi 191 } 192 193 # Switch to the given branch name. 194 function switch_branch 195 { 196 local cmd="git checkout '$1'" 197 printf " %s Switching branch to %s..." "$MARKER" "$1" 198 if [ $DRY_RUN -eq 0 ]; then 199 msg=$( eval "$cmd" 2>&1 ) 200 validate_ret $? "$msg" 201 else 202 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" 203 fi 204 } 205 206 # Checkout a new branch with the given branch name. 207 function new_branch 208 { 209 local cmd="git checkout -b '$1'" 210 printf " %s Creating new branch %s..." "$MARKER" "$1" 211 if [ $DRY_RUN -eq 0 ]; then 212 msg=$( eval "$cmd" 2>&1 ) 213 validate_ret $? "$msg" 214 else 215 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" 216 fi 217 } 218 219 # Switch to an existing branch, or checkout a new branch with the given 220 # branch name. 221 function switch_or_new_branch 222 { 223 local cmd="git rev-parse --verify '$1'" 224 if [ $DRY_RUN -eq 0 ]; then 225 # Call switch_branch if there is a branch, or new_branch if there is not 226 msg=$( eval "$cmd" 2>&1 ) 227 RET=$? 228 if [ $RET -eq 0 ]; then 229 # Branch: (commit id) 230 switch_branch "$1" 231 elif [ $RET -eq 128 ]; then 232 # Not a branch: "fatal: Needed a single revision" 233 new_branch "$1" 234 else 235 # Unexpected return value 236 validate_ret $RET "$msg" 237 fi 238 else 239 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}, then depending on the result:" 240 switch_branch "$1" 241 new_branch "$1" 242 fi 243 } 244 245 # Pull the given branch name. 246 function pull_branch 247 { 248 local cmd="git pull" 249 printf " %s Pulling branch %s..." "$MARKER" "$1" 250 if [ $DRY_RUN -eq 0 ]; then 251 msg=$( eval "$cmd" 2>&1 ) 252 validate_ret $? "$msg" 253 else 254 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" 255 fi 256 } 257 258 # Merge the given branch name ($1) into the current branch ($2). 259 function merge_branch 260 { 261 local cmd="git merge --no-edit '$1'" 262 printf " %s Merging branch %s into %s..." "$MARKER" "$1" "$2" 263 if [ $DRY_RUN -eq 0 ]; then 264 msg=$( eval "$cmd" 2>&1 ) 265 validate_ret $? "$msg" 266 else 267 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" 268 fi 269 } 270 271 # Merge origin/(branch name) into the current branch. 272 function merge_branch_origin 273 { 274 local cmd="git merge --ff-only 'origin/$1'" 275 printf " %s Merging branch origin/%s..." "$MARKER" "$1" 276 if [ $DRY_RUN -eq 0 ]; then 277 msg=$( eval "$cmd" 2>&1 ) 278 validate_ret $? "$msg" 279 else 280 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" 281 fi 282 } 283 284 # Go into the worktree repository. 285 function goto_repo 286 { 287 if [ ! -d "$1" ]; then 288 echo " $1: Not found. Stopping." 289 exit 1 290 fi 291 cd "$1" || exit 292 } 293 294 # Fetch the origin. No arguments. 295 function fetch_origin 296 { 297 local cmd="git fetch origin" 298 printf " %s Fetching origin..." "$MARKER" 299 if [ $DRY_RUN -eq 0 ]; then 300 msg=$( eval "$cmd" 2>&1 ) 301 validate_ret $? "$msg" 302 else 303 printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" 304 fi 305 } 306 307 ############### 308 # Entry point # 309 ############### 310 311 # First, fetch the origin. 312 goto_repo "$ORIGIN_PATH" 313 fetch_origin 314 315 # Go over all configured worktree. 316 for ((i=0; i<COUNT; i++)); do 317 current=${!WORKTREE[$i]:0:1} 318 previous=${!WORKTREE[$i]:1:1} 319 repo_path=${!WORKTREE[$i]:2:1} 320 # default to merge forward mode 321 test_current= 322 test_previous= 323 target_current="$current" 324 target_previous="$previous" 325 if [ "$TEST_BRANCH_PREFIX" ]; then 326 test_current_suffix=${!WORKTREE[$i]:3:1} 327 test_current=${TEST_BRANCH_PREFIX}${test_current_suffix} 328 # the current test branch, if present, or maint/release branch, if not 329 target_current="$test_current" 330 test_previous_suffix=${!WORKTREE[$i]:4:1} 331 if [ "$test_previous_suffix" ]; then 332 test_previous=${TEST_BRANCH_PREFIX}${test_previous_suffix} 333 # the previous test branch, if present, or maint/release branch, if not 334 target_previous="$test_previous" 335 fi 336 fi 337 338 printf "%s Handling branch \\n" "$MARKER" "${BYEL}$target_current${CNRM}" 339 340 # Go into the worktree to start merging. 341 goto_repo "$repo_path" 342 if [ "$test_current" ]; then 343 if [ $USE_EXISTING -eq 0 ]; then 344 # Create a test branch from the currently checked-out branch/commit 345 # Fail if it already exists 346 new_branch "$test_current" 347 else 348 # Switch if it exists, or create if it does not 349 switch_or_new_branch "$test_current" 350 fi 351 fi 352 # Checkout the current maint/release branch 353 switch_branch "$current" 354 # Update the current maint/release branch with an origin merge to get the 355 # latest updates 356 merge_branch_origin "$current" 357 if [ "$test_current" ]; then 358 # Checkout the test branch 359 switch_branch "$test_current" 360 # Merge the updated maint branch into the test branch 361 merge_branch "$current" "$test_current" 362 fi 363 # Merge the previous branch into the target branch 364 # Merge Forward Example: 365 # merge maint-0.3.5 into maint-0.4.0. 366 # Test Branch Example: 367 # merge bug99999_035 into bug99999_040. 368 # Skip the merge if the previous branch does not exist 369 # (there's nothing to merge forward into the oldest test branch) 370 if [ "$target_previous" ]; then 371 merge_branch "$target_previous" "$target_current" 372 fi 373 done