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の言うがままにタイプしていては、リーナス・トーバルズに負けたということになってしまいます。
そこで自分はこんな感じにプロンプトを出すようにして今の所満足しています。
—追記—
git push origin HEADでローカルブランチと同じ名前のリモートブランチにpushできるよ! -uのときも使える
— そな太 (@sonatard) December 21, 2018
まじで!? と思ってドキュメントを確認したら本当に書いてあった。
なんというか、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
を見てもとくにそれっぽい記述がないのでよくわからないのですが…
ドキュメントを読み込む力がないですね… どなたかわかる方いれば教えてください :bow:
—追記おわり—
コンセプトを伝えたいだけの記事なので現場からは以上ですという気持ちなのですが、一応ソースコードも載せておきます。
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コマンドを使っていない自分みたいなクソザコなんかはこんな感じで自動的にリモートブランチもサジェストしてあげるようにしています。
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日目の記事でした。なんかたくさん空いてたしひとつくらい適当に書くかと思って書いて、今見直したら残り枠一つでした。
おしまい。
-
煽っているのではなく、本当にメリット/デメリットが存在しますし、なによりパイプのためにForkするというのはきれいで好きです。 ↩︎