pr 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. #!/usr/bin/env bash
  2. # Land a pull request
  3. # Creates a PR-### branch, pulls the commits, opens up an interactive rebase to
  4. # squash, and then annotates the commit with the changelog goobers
  5. #
  6. # Usage:
  7. # pr <url|number> [<upstream remote>=origin]
  8. main () {
  9. if [ "$1" = "finish" ]; then
  10. shift
  11. finish "$@"
  12. return $?
  13. fi
  14. local url="$(prurl "$@")"
  15. local num=$(basename $url)
  16. local prpath="${url#git@github.com:}"
  17. local repo=${prpath%/pull/$num}
  18. local prweb="https://github.com/$prpath"
  19. local root="$(prroot "$url")"
  20. local api="https://api.github.com/repos/${repo}/pulls/${num}"
  21. local user=$(curl -s $api | json user.login)
  22. local ref="$(prref "$url" "$root")"
  23. local curhead="$(git show --no-patch --pretty=%H HEAD)"
  24. local curbranch="$(git rev-parse --abbrev-ref HEAD)"
  25. local cleanlines
  26. IFS=$'\n' cleanlines=($(git status -s -uno))
  27. if [ ${#cleanlines[@]} -ne 0 ]; then
  28. echo "working dir not clean" >&2
  29. IFS=$'\n' echo "${cleanlines[@]}" >&2
  30. echo "aborting PR merge" >&2
  31. fi
  32. # ok, ready to rock
  33. branch=PR-$num
  34. if [ "$curbranch" == "$branch" ]; then
  35. echo "already on $branch, you're on your own" >&2
  36. return 1
  37. fi
  38. me=$(git config github.user || git config user.name)
  39. if [ "$me" == "" ]; then
  40. echo "run 'git config --add github.user <username>'" >&2
  41. return 1
  42. fi
  43. exists=$(git show --no-patch --pretty=%H $branch 2>/dev/null)
  44. if [ "$exists" == "" ]; then
  45. git fetch origin pull/$num/head:$branch
  46. git checkout $branch
  47. else
  48. git checkout $branch
  49. git pull --rebase origin pull/$num/head
  50. fi
  51. git rebase -i $curbranch # squash and test
  52. if [ $? -eq 0 ]; then
  53. finish "${curbranch}"
  54. else
  55. echo "resolve conflicts and run: $0 finish "'"'${curbranch}'"'
  56. fi
  57. }
  58. # add the PR-URL to the last commit, after squashing
  59. finish () {
  60. if [ $# -eq 0 ]; then
  61. echo "Usage: $0 finish <branch> (while on a PR-### branch)" >&2
  62. return 1
  63. fi
  64. local curbranch="$1"
  65. local ref=$(cat .git/HEAD)
  66. local prnum
  67. case $ref in
  68. "ref: refs/heads/PR-"*)
  69. prnum=${ref#ref: refs/heads/PR-}
  70. ;;
  71. *)
  72. echo "not on the PR-## branch any more!" >&2
  73. return 1
  74. ;;
  75. esac
  76. local me=$(git config github.user || git config user.name)
  77. if [ "$me" == "" ]; then
  78. echo "run 'git config --add github.user <username>'" >&2
  79. return 1
  80. fi
  81. set -x
  82. local url="$(prurl "$prnum")"
  83. local num=$prnum
  84. local prpath="${url#git@github.com:}"
  85. local repo=${prpath%/pull/$num}
  86. local prweb="https://github.com/$prpath"
  87. local root="$(prroot "$url")"
  88. local api="https://api.github.com/repos/${repo}/pulls/${num}"
  89. local user=$(curl -s $api | json user.login)
  90. local lastmsg="$(git log -1 --pretty=%B)"
  91. local newmsg="${lastmsg}
  92. PR-URL: ${prweb}
  93. Credit: @${user}
  94. Close: #${num}
  95. Reviewed-by: @${me}
  96. "
  97. git commit --amend -m "$newmsg"
  98. git checkout $curbranch
  99. git merge PR-${prnum} --ff-only
  100. set +x
  101. }
  102. prurl () {
  103. local url="$1"
  104. if [ "$url" == "" ] && type pbpaste &>/dev/null; then
  105. url="$(pbpaste)"
  106. fi
  107. if [[ "$url" =~ ^[0-9]+$ ]]; then
  108. local us="$2"
  109. if [ "$us" == "" ]; then
  110. us="origin"
  111. fi
  112. local num="$url"
  113. local o="$(git config --get remote.${us}.url)"
  114. url="${o}"
  115. url="${url#(git:\/\/|https:\/\/)}"
  116. url="${url#git@}"
  117. url="${url#github.com[:\/]}"
  118. url="${url%.git}"
  119. url="https://github.com/${url}/pull/$num"
  120. fi
  121. url=${url%/commits}
  122. url=${url%/files}
  123. url="$(echo $url | perl -p -e 's/#issuecomment-[0-9]+$//g')"
  124. local p='^https:\/\/github.com\/[^\/]+\/[^\/]+\/pull\/[0-9]+$'
  125. if ! [[ "$url" =~ $p ]]; then
  126. echo "Usage:"
  127. echo " $0 <pull req url>"
  128. echo " $0 <pull req number> [<remote name>=origin]"
  129. type pbpaste &>/dev/null &&
  130. echo "(will read url/id from clipboard if not specified)"
  131. exit 1
  132. fi
  133. url="${url/https:\/\/github\.com\//git@github.com:}"
  134. echo "$url"
  135. }
  136. prroot () {
  137. local url="$1"
  138. echo "${url/\/pull\/+([0-9])/}"
  139. }
  140. prref () {
  141. local url="$1"
  142. local root="$2"
  143. echo "refs${url:${#root}}/head"
  144. }
  145. main "$@"