You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

github-merge.sh 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/bin/bash
  2. # This script will locally construct a merge commit for a pull request on a
  3. # github repository, inspect it, sign it and optionally push it.
  4. # The following temporary branches are created/overwritten and deleted:
  5. # * pull/$PULL/base (the current master we're merging onto)
  6. # * pull/$PULL/head (the current state of the remote pull request)
  7. # * pull/$PULL/merge (github's merge)
  8. # * pull/$PULL/local-merge (our merge)
  9. # In case of a clean merge that is accepted by the user, the local branch with
  10. # name $BRANCH is overwritten with the merged result, and optionally pushed.
  11. REPO="$(git config --get githubmerge.repository)"
  12. if [[ "d$REPO" == "d" ]]; then
  13. echo "ERROR: No repository configured. Use this command to set:" >&2
  14. echo "git config githubmerge.repository <owner>/<repo>" >&2
  15. echo "In addition, you can set the following variables:" >&2
  16. echo "- githubmerge.host (default git@github.com)" >&2
  17. echo "- githubmerge.branch (default master)" >&2
  18. echo "- githubmerge.testcmd (default none)" >&2
  19. exit 1
  20. fi
  21. HOST="$(git config --get githubmerge.host)"
  22. if [[ "d$HOST" == "d" ]]; then
  23. HOST="git@github.com"
  24. fi
  25. BRANCH="$(git config --get githubmerge.branch)"
  26. if [[ "d$BRANCH" == "d" ]]; then
  27. BRANCH="master"
  28. fi
  29. TESTCMD="$(git config --get githubmerge.testcmd)"
  30. PULL="$1"
  31. if [[ "d$PULL" == "d" ]]; then
  32. echo "Usage: $0 pullnumber [branch]" >&2
  33. exit 2
  34. fi
  35. if [[ "d$2" != "d" ]]; then
  36. BRANCH="$2"
  37. fi
  38. # Initialize source branches.
  39. git checkout -q "$BRANCH"
  40. if git fetch -q "$HOST":"$REPO" "+refs/pull/$PULL/*:refs/heads/pull/$PULL/*"; then
  41. if ! git log -q -1 "refs/heads/pull/$PULL/head" >/dev/null 2>&1; then
  42. echo "ERROR: Cannot find head of pull request #$PULL on $HOST:$REPO." >&2
  43. exit 3
  44. fi
  45. if ! git log -q -1 "refs/heads/pull/$PULL/merge" >/dev/null 2>&1; then
  46. echo "ERROR: Cannot find merge of pull request #$PULL on $HOST:$REPO." >&2
  47. exit 3
  48. fi
  49. else
  50. echo "ERROR: Cannot find pull request #$PULL on $HOST:$REPO." >&2
  51. exit 3
  52. fi
  53. if git fetch -q "$HOST":"$REPO" +refs/heads/"$BRANCH":refs/heads/pull/"$PULL"/base; then
  54. true
  55. else
  56. echo "ERROR: Cannot find branch $BRANCH on $HOST:$REPO." >&2
  57. exit 3
  58. fi
  59. git checkout -q pull/"$PULL"/base
  60. git branch -q -D pull/"$PULL"/local-merge 2>/dev/null
  61. git checkout -q -b pull/"$PULL"/local-merge
  62. TMPDIR="$(mktemp -d -t ghmXXXXX)"
  63. function cleanup() {
  64. git checkout -q "$BRANCH"
  65. git branch -q -D pull/"$PULL"/head 2>/dev/null
  66. git branch -q -D pull/"$PULL"/base 2>/dev/null
  67. git branch -q -D pull/"$PULL"/merge 2>/dev/null
  68. git branch -q -D pull/"$PULL"/local-merge 2>/dev/null
  69. rm -rf "$TMPDIR"
  70. }
  71. # Create unsigned merge commit.
  72. (
  73. echo "Merge pull request #$PULL"
  74. echo ""
  75. git log --no-merges --topo-order --pretty='format:%h %s (%an)' pull/"$PULL"/base..pull/"$PULL"/head
  76. )>"$TMPDIR/message"
  77. if git merge -q --commit --no-edit --no-ff -m "$(<"$TMPDIR/message")" pull/"$PULL"/head; then
  78. if [ "d$(git log --pretty='format:%s' -n 1)" != "dMerge pull request #$PULL" ]; then
  79. echo "ERROR: Creating merge failed (already merged?)." >&2
  80. cleanup
  81. exit 4
  82. fi
  83. else
  84. echo "ERROR: Cannot be merged cleanly." >&2
  85. git merge --abort
  86. cleanup
  87. exit 4
  88. fi
  89. # Run test command if configured.
  90. if [[ "d$TESTCMD" != "d" ]]; then
  91. # Go up to the repository's root.
  92. while [ ! -d .git ]; do cd ..; done
  93. if ! $TESTCMD; then
  94. echo "ERROR: Running $TESTCMD failed." >&2
  95. cleanup
  96. exit 5
  97. fi
  98. # Show the created merge.
  99. git diff pull/"$PULL"/merge..pull/"$PULL"/local-merge >"$TMPDIR"/diff
  100. git diff pull/"$PULL"/base..pull/"$PULL"/local-merge
  101. if [[ "$(<"$TMPDIR"/diff)" != "" ]]; then
  102. echo "WARNING: merge differs from github!" >&2
  103. read -p "Type 'ignore' to continue. " -r >&2
  104. if [[ "d$REPLY" =~ ^d[iI][gG][nN][oO][rR][eE]$ ]]; then
  105. echo "Difference with github ignored." >&2
  106. else
  107. cleanup
  108. exit 6
  109. fi
  110. fi
  111. read -p "Press 'd' to accept the diff. " -n 1 -r >&2
  112. echo
  113. if [[ "d$REPLY" =~ ^d[dD]$ ]]; then
  114. echo "Diff accepted." >&2
  115. else
  116. echo "ERROR: Diff rejected." >&2
  117. cleanup
  118. exit 6
  119. fi
  120. else
  121. # Verify the result.
  122. echo "Dropping you on a shell so you can try building/testing the merged source." >&2
  123. echo "Run 'git diff HEAD~' to show the changes being merged." >&2
  124. echo "Type 'exit' when done." >&2
  125. bash -i
  126. read -p "Press 'm' to accept the merge. " -n 1 -r >&2
  127. echo
  128. if [[ "d$REPLY" =~ ^d[Mm]$ ]]; then
  129. echo "Merge accepted." >&2
  130. else
  131. echo "ERROR: Merge rejected." >&2
  132. cleanup
  133. exit 7
  134. fi
  135. fi
  136. # Sign the merge commit.
  137. read -p "Press 's' to sign off on the merge. " -n 1 -r >&2
  138. echo
  139. if [[ "d$REPLY" =~ ^d[Ss]$ ]]; then
  140. if [[ "$(git config --get user.signingkey)" == "" ]]; then
  141. echo "WARNING: No GPG signing key set, not signing. Set one using:" >&2
  142. echo "git config --global user.signingkey <key>" >&2
  143. git commit -q --signoff --amend --no-edit
  144. else
  145. git commit -q --gpg-sign --amend --no-edit
  146. fi
  147. fi
  148. # Clean up temporary branches, and put the result in $BRANCH.
  149. git checkout -q "$BRANCH"
  150. git reset -q --hard pull/"$PULL"/local-merge
  151. cleanup
  152. # Push the result.
  153. read -p "Type 'push' to push the result to $HOST:$REPO, branch $BRANCH. " -r >&2
  154. if [[ "d$REPLY" =~ ^d[Pp][Uu][Ss][Hh]$ ]]; then
  155. git push "$HOST":"$REPO" refs/heads/"$BRANCH"
  156. fi