50
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

エーピーコミュニケーションズAdvent Calendar 2019

Day 3

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

Last updated at Posted at 2019-12-02

はじめに

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

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

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

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

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

ゴール

ここでは便宜上"sample"という名前の適当なリポジトリを作成し、空のファイルを1つ作成しただけのコミットを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 を見やすくする

解法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行で済ませる◎

お待たせしました。こっちが本題です。
解法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

これを見ると、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の仕組み

雑にまとめると、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を使いこなす方法

余談3. git pushについてもう少し

手元で解法1を検証してから解法2の検証に移る時、

git push -f origin b49190e:master

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

【やっとわかった!】gitのHEAD^とHEAD~の違い

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

余談4. "@"

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

Git 1.8.5 最新情報 | Atlassian Blogs

#参考文献
Git ユーザマニュアル (バージョン 1.5.3 以降用)
【git stash】コミットはせずに変更を退避したいとき
git push の取り消し方法 | WWWクリエイターズ
Git の仕組み (1) - こせきの技術日記

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

50
24
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
50
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?