tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

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