【Git】ブランチを途中のコミットまでpushしたい

# はじめに
初めまして。半年ほど前に未経験からRails中心の開発にアサインされた新卒社員のyagaodekawasuです。

さて、Gitでコード管理をしながらチーム開発していたある日、作業途中のブランチについて先輩社員から「**現状把握したいから、取り敢えず今できてるとこまでpushして**」と言われました。

その時のブランチはHEADがスナップショットのための中途半端なコミットだったので、最新の一つ前までのコミットをpushしたい（下図参照）と思ったのですが、「**git push 途中まで**」とかのワードでググっても求めているソリューションにたどり着くことができませんでした。

先輩達の助言を得てなんとか目的を達成することができたものの、また同じようなことがあった時のことを考えて、忘れないうちにインターネッツに書き留めておこうと思います。

```pushするブランチ
A--B--C--D
      ↑  ↑このコミットは作業途中でゴチャゴチャしてるから
      └ A~Cをpushしたい
```

# ゴール
ここでは便宜上"sample"という名前の適当なリポジトリを作成し、**空のファイルを１つ作成しただけのコミット**を4つ用意して説明することとします。初期状態では、リモートブランチには最初のコミットだけがpushされています。
ここから"Make piyo"までのコミットをpushすることが今回のゴールです。

```
$ git tree
*       yagaodekawasu      64a1dce  (HEAD -> master) Make hogehoge
*       yagaodekawasu      06b4153  Make piyo
*       yagaodekawasu      63c971c  Make fuga
*       yagaodekawasu      b49190e  (origin/master) Make hoge
```

なお、ここで叩いている**git tree**は以下の記事で紹介されている拡張された**git log**コマンドのエイリアスです。非常に便利ですのでまだ導入されていない方はこの機会にどうぞ。

[git log を見やすくする](https://qiita.com/takasianpride/items/842a785af610025a2030)

# 解法1. stashを使う△
最初に試した方法です。
コミットしていない変更点は、ステージング（add）しているかどうかに関わらず**git stash save**によってスタックに退避させることができます。
pushが完了したら、 **git stash pop stash@{n}** で元に戻します。

```
// 最新のコミットを取り消してステージング状態に戻す
$ git reset --soft HEAD~

// 戻ってることを確認
$ git status
ブランチ master
コミット予定の変更点:
  (use "git reset HEAD <file>..." to unstage)

	new file:   hogehoge

// 未コミットの変更を退避
$ git stash save
Saved working directory and index state WIP on master: 06b4153 Make piyo

// stashの中身を確認
$ git stash list
stash@{0}: WIP on master: 06b4153 Make piyo

// pushする
$ git push origin HEAD
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 407 bytes | 407.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To github.com:yagaodekawasu/sample.git
   b49190e..06b4153  HEAD -> master

// 確認
$ git log
commit 06b415305173622c08ea88823dadcf5c378595b6 (HEAD -> master, origin/master)
Author: yagaodekawasu
Date:   Mon Dec 2 10:15:46 2019 +0900

    Make piyo

commit 63c971c71b7400262108a6548ee5148d9569828d
Author: yagaodekawasu
Date:   Mon Dec 2 10:15:24 2019 +0900

    Make fuga

commit b49190ec49ce28ea8762a1b234224cd9705d3c36
Author: yagaodekawasu
Date:   Mon Dec 2 10:13:34 2019 +0900

    Make hoge

// 退避させた変更を元に戻す
$ git stash pop stash@{0}
ブランチ master
コミット予定の変更点:
  (use "git reset HEAD <file>..." to unstage)

	new file:   hogehoge

Dropped stash@{0} (ecf2c08c7c1df809407255b775f39d700feafe39)
```

上手くいきましたね。
ただ工数が多いのと、2つ以上前までのコミットをpushしたい場合に対応できない（多分不可能ではないがとても面倒）のが難点です。stashした変更を再度popし直さないといけないのもイケてません。
まぁstashの練習にはなるかな、という感じですね。

# 解法2. 1行で済ませる◎
お待たせしました。こっちが本題です。
解法１を社内Slackに載せて「**できたぜ！！ﾄﾞﾔｧ...**」していたら、先輩が（っ'-')╮=͟͟͞͞　ｽｯとこんなコマンドを投げて寄越しました。

```
git push origin HEAD~:<remote-branch>
```
思わず「**そマ！？**」と返信してしまいました。不敬罪で減給されなくてよかったです。
取り敢えず、先ほどの状態から半信半疑でコマンドを叩いてみます。

```
$ git tree
*       yagaodekawasu      3965f49  (HEAD -> master) Make hogehoge
*       yagaodekawasu      06b4153  Make piyo
*       yagaodekawasu      63c971c  Make fuga
*       yagaodekawasu      b49190e  (origin/master) Make hoge

$ git push origin HEAD~:master
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 407 bytes | 407.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To github.com:yagaodekawasu/sample.git
   b49190e..06b4153  HEAD~ -> master

$ git log
commit 3965f49e9d1fe43ce0ecaa56c7a287a1dc4c6466 (HEAD -> master)
Author: yagaodekawasu
Date:   Mon Dec 2 11:31:20 2019 +0900

    Make hogehoge

commit 06b415305173622c08ea88823dadcf5c378595b6 (origin/master)
Author: yagaodekawasu
Date:   Mon Dec 2 10:15:46 2019 +0900

    Make piyo

commit 63c971c71b7400262108a6548ee5148d9569828d
Author: yagaodekawasu
Date:   Mon Dec 2 10:15:24 2019 +0900

    Make fuga

commit b49190ec49ce28ea8762a1b234224cd9705d3c36
Author: yagaodekawasu
Date:   Mon Dec 2 10:13:34 2019 +0900

    Make hoge
```
ははぁ〜、上手くいってますねぇ。。。あの血の滲むような努力は何だったのか。。。
この方法なら、HEAD~をHEAD~2にすれば最新の2つ前のコミットまでをpushできるような拡張性が担保されます。

# どういう仕組み？
上手く行ったのは良かったんですが、気になるのは「**どうしてこういう書き方ができるのか？**」。
私がGitを習得するために参考にしたサイトでは、**git push**にこんな文法があるなんて書いてなかったような...
で、公式ドキュメントを参照しました（**git push --help**でも見れます）。

[Git - git-push Documentation](https://git-scm.com/docs/git-push)

これを見ると、**git push**の汎用的な文法規則は

```
git push [<options>] [<repository> [<refspec>…​]]
```
であることがわかります。オプションの他にはリポジトリと参照点を引数として渡せるということですね。
で、ここで問題となるのは\<refspec\>の部分ですが、この\<refspec\>は細分化すると

```
<refspec>…​ = +<src>:<dst>
```
と書けるようです（"+"はここではあまり重要ではなさそうなので割愛）。

\<src\>にはpushしたいブランチ・特定のコミットのハッシュ値またはそのエイリアス（"master~4"や"HEAD"など）が入ります。

\<dst\>はこのpushによってリモートのどのブランチが更新されるかを表します（コミットのハッシュ値・エイリアス不可）。

というわけで、先ほどの

```
git push origin HEAD~:master
```

というコマンドは、「**"origin"という名前が付けられたリモートリポジトリの"master"というブランチが、ローカルの"HEAD~"までを参照する**」という意味だったんですね。
ちなみに「HEAD~」は「@~」とも書けるし、pushするブランチがmasterの場合「master~」でも同じことです。

# 結論
やっぱマニュアルをちゃんと読もう。

# 余談1. git stashとは何か？
解法1で**git stash save**とか**git stash pop**とか叩いて、結果確認して「上手くできた。良かった。」とかしてましたが、やっぱりちゃんと理解して使わないといつか足下を掬われる気がするので調べてみました。
**git stash save**した状態でログを確認すると、このような表示になります。

```
$ git tree
*       yagaodekawasu      0d576db  (refs/stash) WIP on master: 06b4153 Make piyo
|\  
| *     yagaodekawasu      dad1769  index on master: 06b4153 Make piyo
|/  
*       yagaodekawasu      06b4153  (HEAD -> master, origin/master) Make piyo
*       yagaodekawasu      63c971c  Make fuga
*       yagaodekawasu      b49190e  Make hoge
```
・・・どゆこと？　と思ってググった結果、この記事にたどり着きました。

[サクッと使いこなすためのgit stash Tips & stashの仕組み#おまけ---stashの仕組み](https://qiita.com/ton1517/items/9888a78f6b063e748558#%E3%81%8A%E3%81%BE%E3%81%91---stash%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)

雑にまとめると、**git stash save**は

①HEADからブランチを切ってステージングされた差分（**index**）をまとめたコミットを作成
②同じくHEADから別のブランチを切ってステージングされていない（**WIP**：Work In Progress「作業中」）変更をまとめたコミットを作成
③両者を**refs/stash**にマージ

ということをしているらしいです。賢い。
（①、②で切り出されたブランチ名が何であるかは、今回は調べきれませんでした。）

複数stashしている場合は、最初のstashを0番目として、 **stash@{n}** でアクセスできるって感じですかね。

# 余談2. git resetについて
解法1で**git reset --soft HEAD^**というコマンドを叩きましたが、ここは**--soft**を付けなくても問題ありません。
オプションの有無でどのような違いが生じるかは、以下のエントリで詳しく説明されているので、気になる方は読んでみてください。とても参考になりました。

[[git reset (--hard/--soft)]ワーキングツリー、インデックス、HEADを使いこなす方法](https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe)

# 余談3. git pushについてもう少し
手元で解法1を検証してから解法2の検証に移る時、

```
git push -f origin b49190e:master
```
で初期状態に戻したりしました。
b49190eは最初のコミットのハッシュ値です。"~"や"^"の使い方に自信がない時はハッシュ値で直接指定した方が安全かもしれません。
ちなみに"~"と"^"については以下の記事がわかりやすかったです。

[【やっとわかった！】gitのHEAD^とHEAD~の違い
](https://qiita.com/chihiro/items/d551c14cb9764454e0b9)

別の使い方をすればブランチの削除もできてしまう（むしろそっちの使い方の方が有名？）ようなので、この記法一つ覚えておくとGitでできることがかなり広がるかもしれませんね。

# 余談4. "@"

"HEAD"のエイリアスとして"@"が導入されたのはGit 1.8.5（2013年11月29日リリース）からだそうです。
意外と検索してすぐにヒットしなかったので、覚書程度に。

[Git 1.8.5 最新情報 | Atlassian Blogs](https://japan.blogs.atlassian.com/2013/12/whats-new-git-1-8-5/)

#参考文献
[Git ユーザマニュアル (バージョン 1.5.3 以降用)](https://img.atwikiimg.com/www8.atwiki.jp/git_jp/pub/Documentation.ja/user-manual.html)
[【git stash】コミットはせずに変更を退避したいとき](https://qiita.com/chihiro/items/f373873d5c2dfbd03250)
[git push の取り消し方法 | WWWクリエイターズ](http://www-creators.com/archives/2020)
[Git の仕組み (1) - こせきの技術日記](http://koseki.hatenablog.com/entry/2014/04/22/inside-git-1)

ご清覧ありがとうございました！