LoginSignup
4
2

More than 3 years have passed since last update.

Pull-requestをレビューする時に使う関数を書いた

Last updated at Posted at 2019-06-05

2019.09.12 追記

git-extras というのがあった。このレポジトリの中の git-pr コマンドがまさにやりたかったことだ。皆さんにはぜひこれを使ってほしい。

以下は shell command を function で作るための参考記事として読んでほしい。


概要

以下のコマンド

$ git fetch <remote-name> pull/<ID>/head:<branch-name>

でプルリクエストを簡単に fetch して手元で実行できるらしい。

コマンドがわかりにくいし打ちにくいので、shell function にした。

ついでに、引数でIDremote-nameを受け取れるようにした。

できたもの

(master) $ git-fetch-pull-request 42
Execute git fetch upstream pull/42/head:pull-request/42...
remote: Counting objects: ---, done.
remote: Compressing objects: 100% (---/---), done.
remote: Total --- (delta ---), reused --- (delta ---)
Unpacking objects: 100% (---/---), done.
From https://---
 * [new branch]      refs/pull/42/head -> pull-request/42
Checkout to pull-request/42...
Switched to branch 'pull-request/42'
(pull-request/42) $ 
(pull-request/42) $ git-fetch-pull-request --help
Usage: git-fetch-pull-request [-h,--help] [--upstream UPSTREAM] NUMBER

Fetch pull request to a new branch and checkout to it.

Positional arguments:
  NUMBER (int): id for the target pull request.

Optional arguments:
  -h, --help: show this message and exit.
  --upstream UPSTREAM (str): remote upstream name. default: 'upstream'.
(pull-request/42) $ 

引数を処理する

やりたいこと

IDは必須項目。remote-nameはdefaultを用意して、引数によって変えられるようにしたい。branch-nameは固定で良い。

せっかくなので、<function-name> --help とか打ったら usage が出てほしい。

やったこと

http://dojineko.hateblo.jp/entry/2016/06/30/225113
ここを参考に、こんな感じで実装した。

git-fetch-pull-request() {
  local function_name='git-fetch-pull-request'

  local help="Usage: ${function_name} [-h,--help] [--upstream UPSTREAM] NUMBER

Fetch pull request to a new branch and checkout to it.

Positional arguments:
  NUMBER (int): id for the target pull request.

Optional arguments:
  -h, --help: show this message and exit.
  --upstream UPSTREAM (str): remote upstream name. default: 'upstream'.
  "

  _git-fetch-pull-request() {
    local number=$1
    local upstream=$2

    # ...
    # メイン処理
    # ...

    return 0
  }

  show_help () {
    echo $help
  }

  invalid_arguments () {
    local argument=${1:-}
    echo "${function_name}: illegal option ${argument}"
    show_help
  }

  parameter_required () {
    local argument=${1:-}
    echo "${function_name}: argument required for ${argument}"
    show_help
  }

  # Parse optional arguments
  local upstream='upstream'
  local PARAM=()
  for OPT in "$@"; do
    case $OPT in
      '-h' | '--help' )
        show_help
        return 0
        ;;
      '--upstream' )
        if [[ -z $2 ]] || [[ $2 =~ ^-+ ]]; then
          parameter_required '--upstream'
          return 1
        fi
        upstream=$2
        shift 2
        ;;
      '--' | '-' )
        # Treat following all arguments as positional arguments
        shift
        PARAM+=( "$@" )
        break
        ;;
      -* )
        invalid_arguments $1
        return 1
        ;;
      * )
        if [[ -n $1 ]] && [[ ! $1 =~ ^-+ ]]; then
          PARAM+=( "$1" )
          shift
        fi
        ;;
    esac
  done
  # Get positional arguments
  number=$PARAM; PARAM=( "${PARAM[@]:1}" )
  if [[ -z $number ]]; then
    parameter_required 'NUMBER'
    return 1
  fi
  # Check remains
  if [[ -n "${PARAM[@]}" ]]; then
    invalid_arguments $PARAM
    return 1
  fi

  # Main
  _git-fetch-pull-request ${number} ${upstream}
}

解説

来たオプションを前から順番に処理して、処理できないものはとりあえずPARAMに詰めていく。
途中--upstreamのような追加引数のあるオプションが来た場合、その次まで読んで、その分2回SHIFTする。

最後に残ったPARAMを処理して終了。メイン処理(_git-fetch-pull-request())に入る。

FetchCheckout

やりたいこと

前項で引数をいい感じに処理してIDremote-nameを得たので、それを使ってgit fetchする。

git repository 以外で叩いたときや、存在しないプルリクに対して実行したときには、適切にエラーを吐いて止まってくれるようにする。

やったこと

_git-fetch-pull-request() {
  local number=$1
  local upstream=$2

  local branch_name="pull-request/${number}"
  if [[ -z `git rev-parse --is-inside-work-tree 2> /dev/null` ]]; then
    # Not a git repo
    echo "Not a git repository (or any of the parent directories). Stop."
    return 1
  fi
  if [[ -n `git branch --list ${branch_name}` ]]; then
    # Branch exists
    echo "Branch ${branch_name} already exists. Stop."
    return 1
  fi
  if ! git remote show ${upstream} 2>&1 > /dev/null; then
    # Upstream not exists
    echo "Remote ${upstream} not exists. Stop."
    return 1
  fi

  # Fetch
  local fetching="git fetch upstream pull/${number}/head:${branch_name}"
  echo "Execute ${fetching}..."
  if ! eval ${fetching}; then
    echo "Something went wrong. Stop."
    return 1
  fi

  # Checkout
  local checkout="git checkout ${branch_name}"
  echo "Checkout to ${branch_name}..."
  if ! eval ${checkout}; then
    echo "Something went wrong. Stop."
    return 1
  fi

  return 0
}

何が起こるかわからないので、丁寧にエラー処理をする。
Git の repository にいるかどうかの判定は、ここ ( https://stackoverflow.com/questions/2180270/check-if-current-directory-is-a-git-repository ) を参考にした。

せっかくなので、 checkout もしてもらった。

最終的なコード

以下のようになった。
これを自分の.<shell-name>rcに書き込んで完成。

git-fetch-pull-request() {
  local function_name='git-fetch-pull-request'

  local help="Usage: ${function_name} [-h,--help] [--upstream UPSTREAM] NUMBER

Fetch pull request to a new branch and checkout to it.

Positional arguments:
  NUMBER (int): id for the target pull request.

Optional arguments:
  -h, --help: show this message and exit.
  --upstream UPSTREAM (str): remote upstream name. default: 'upstream'.
  "

  _git-fetch-pull-request() {
    local number=$1
    local upstream=$2

    local branch_name="pull-request/${number}"
    if [[ -z `git rev-parse --is-inside-work-tree 2> /dev/null` ]]; then
      # Not a git repo
      echo "Not a git repository (or any of the parent directories). Stop."
      return 1
    fi
    if [[ -n `git branch --list ${branch_name}` ]]; then
      # Branch exists
      echo "Branch ${branch_name} already exists. Stop."
      return 1
    fi
    if ! git remote show ${upstream} 2>&1 > /dev/null; then
      # Upstream not exists
      echo "Remote ${upstream} not exists. Stop."
      return 1
    fi

    # Fetch
    local fetching="git fetch upstream pull/${number}/head:${branch_name}"
    echo "Execute ${fetching}..."
    if ! eval ${fetching}; then
      echo "Something went wrong. Stop."
      return 1
    fi

    # Checkout
    local checkout="git checkout ${branch_name}"
    echo "Checkout to ${branch_name}..."
    if ! eval ${checkout}; then
      echo "Something went wrong. Stop."
      return 1
    fi

    return 0
  }

  show_help () {
    echo $help
  }

  invalid_arguments () {
    local argument=${1:-}
    echo "${function_name}: illegal option ${argument}"
    show_help
  }

  parameter_required () {
    local argument=${1:-}
    echo "${function_name}: argument required for ${argument}"
    show_help
  }

  # Parse optional arguments
  local upstream='upstream'
  local PARAM=()
  for OPT in "$@"; do
    case $OPT in
      '-h' | '--help' )
        show_help
        return 0
        ;;
      '--upstream' )
        if [[ -z $2 ]] || [[ $2 =~ ^-+ ]]; then
          parameter_required '--upstream'
          return 1
        fi
        upstream=$2
        shift 2
        ;;
      '--' | '-' )
        # Treat following all arguments as positional arguments
        shift
        PARAM+=( "$@" )
        break
        ;;
      -* )
        invalid_arguments $1
        return 1
        ;;
      * )
        if [[ -n $1 ]] && [[ ! $1 =~ ^-+ ]]; then
          PARAM+=( "$1" )
          shift
        fi
        ;;
    esac
  done
  # Get positional arguments
  number=$PARAM; PARAM=( "${PARAM[@]:1}" )
  if [[ -z $number ]]; then
    parameter_required 'NUMBER'
    return 1
  fi
  # Check remains
  if [[ -n "${PARAM[@]}" ]]; then
    invalid_arguments $PARAM
    return 1
  fi

  # Main
  _git-fetch-pull-request ${number} ${upstream}
}
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2