git push origin masterを絶対に手打ちしないために

この記事はAizu Advent Calendar 2018の10日目の記事です。

雰囲気でシェルをやっているクソザコです。

$ git push
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master

初回にgit pushを打つとこのようなエラーが出ますよね。このメッセージどおり git push --set-upstream origin master と打てば二回以降は origin/master をデフォルトとして扱ってくれます。

が、Gitの言うがままにタイプしていては、リーナス・トーバルズに負けたということになってしまいます。

そこで自分はこんな感じにプロンプトを出すようにして今の所満足しています。

/assets/igp/screenshot.png

—追記—

まじで!? と思ってドキュメントを確認したら本当に書いてあった。

なんというか、HEADはローカルのリビジョンを指すときのみに使用できるイメージがあったので、これには驚きだった。

ちゃんとこんなふうに書いても動く。

git push origin HEAD:HEAD

ちなみにちなみにHEADの内容をそのまま展開しても動く。

$ cat .git/HEAD
refs: refs/heads/master
$ git push origin refs/heads/master:refs/heads/master
...

リモートの指定は単にブランチ名だと思っていたので、ちゃんとリビジョン指定が普通にできることが驚き(ドキュメントによるとハッシュ値でなければいけるらしい)。

ただ一つ謎なのは、HEADの省略形であるはずの @ では動かないこと。

$ git push origin @
fatal: invalid refspec '@'

man gitrevisions を見てもとくにそれっぽい記述がないのでよくわからないのですが…

ドキュメントを読み込む力がないですね… どなたかわかる方いれば教えてください 🙇

—追記おわり—

コンセプトを伝えたいだけの記事なので現場からは以上ですという気持ちなのですが、一応ソースコードも載せておきます。

function git
    if test -z "$argv"
        command git
        return
    end

    switch $argv[1]
        case push
            set -l head
            if not string join \n -- $argv | sed 1d | grep -E '^[^-]' >/dev/null
                and command git status -b --porcelain=v2 | grep -E 'upstream|head' | cut -d' ' -f3 | begin
                    read head
                    and not read -l upstream
                end
                and command git remote get-url origin >/dev/null
                and read -P"Set origin/$head as the upstream branch? [Y/n]: " -l ans
                and contains "$ans" y Y ''

                command git $argv -u origin $head
            else
                command git $argv
            end
        case '*'
            command git $argv
    end
end

↑はFishです。今回ブログを書くにあたって、Bashでも書いてみました。

function git {
    case $1 in
        push )
            local ans
            local stat
            local head
            local subargs=( "$@" )
            subargs=("${argv[@]:2}")
            subargs=("${subargs[@]##-*}")
            if [ "${#subargs[@]}" = 0 ] \
                && mapfile -t stat < <(command git status -b --porcelain=v2 | grep -E 'upstream|head' | cut -d' ' -f3) \
                && [ "${#stat[@]}" = 1 ] && head=${stat[0]} \
                && command git remote get-url origin >/dev/null \
                && read -p"Set origin/$head as the upstream branch? [Y/n]: " ans \
                && { [ "$ans" = y ] || [ "$ans" = Y ] || [ -z "$ans" ]; }; then

                command git "$@" -u origin "$head"
            else
                command git "$@"
            fi
            ;;
       * )
            command git "$@"
            ;;
    esac
}

Shellcheckって便利ですね。Bashはほぼ初心者なので、もっとBashらしくなる改善点などなどあればぜひコメントください👰

おまけ

リポジトリを作ったばかりのときはリモートブランチさえ登録していない状態かと思います。hubコマンド等でリポジトリを作る人は同時にリモートブランチを紐づけてあげるスクリプトを書けば良いと思いますが、なぜかhubコマンドを使っていない自分みたいなクソザコなんかはこんな感じで自動的にリモートブランチもサジェストしてあげるようにしています。

/assets/igp/suggest_remote.svg

GitHubのリポジトリ名をパスから取ってきているだけです。

Fish: 先程の case push の下に

set -l ghuser acomagu
if test -z (git remote)
    and git rev-parse --show-toplevel | sed 's|^'(ghq root)"/github.com/$ghuser/\(.*\)\$|\1|" | read -l dirname
    and read -P"Set git@github.com:$ghuser/$dirname as origin remote branch? [Y/n]: " -l ans
    and contains "$ans" y Y ''

    command git remote add origin git@github.com:$ghuser/$dirname
end

を追加です。Bashですとおんなじようなあたりに

local ghuser=acomagu
local ans
if [ -z "$(git remote)" ] \
    && dirname=$(git rev-parse --show-toplevel | sed "s|^$(ghq root)/github.com/$ghuser/\(.*\)\$|\1|") \
    && read -p"Set git@github.com:$ghuser/$dirname as origin remote branch? [Y/n]: " ans \
    && { [ "$ans" = y ] || [ "$ans" = Y ] || [ -z "$ans" ]; }; then

    command git remote add origin "git@github.com:$ghuser/$dirname"
fi

という感じだと思います。サブシェルも一長一短だと感じる今日この頃です(Fishにはサブシェルがありません)1

この記事はAizu Advent Calendarの10日目の記事でした。なんかたくさん空いてたしひとつくらい適当に書くかと思って書いて、今見直したら残り枠一つでした。

おしまい。


  1. 煽っているのではなく、本当にメリット/デメリットが存在しますし、なによりパイプのためにForkするというのはきれいで好きです。 [return]