git-merge-forward.sh 12 KB

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