雑記

いち情報系大学生

Learn git branchingを一通り解いたので、その解答と所感

最近gitを学び直しました。
Learn git branchingを解いたので、その答えを書いておきます。

解いている途中で気がついたのですが...Learn git branchingには新版と旧版があり、旧版はバグで解けない問題があるそう。今回僕は4-1まで旧版で解いて、それ以降は新版で解いています。ひょっとしたら新と旧で問題も違うかも...。
(旧:https://k.swd.cc/learnGitBranching-ja/
 新:https://learngitbranching.js.org/?locale=ja


intro1 (1-1)

$ git commit
$ git commit

やるだけ。


intro2(1-2)

$ git branch bugFix
$ git checkout bugFix

ブランチ切ってcheckoutしてねという問題。
これもチュートリアルに沿ってやるだけ。


intro3(1-3)

$ git branch bugFix
$ git checkout bugFix    
$ git commit    
$ git checkout master    
$ git commit    
$ git merge bugFix

...で良いんだけども、「この問題は最短5回のコマンド実行で解けるよ」とか言われた。結論からいうと、先頭2行分を以下のように置き換えることができた。

$ git checkout -b bugFix

git checkoutに-bオプションをつけることで「ブランチ作成」と「切り替え」を1度に行うことができる。
チュートリアルではオプションについて一切触れていなかったので...その点は初学者にちょっぴり不親切だなぁとは思った。


intro4(1-4)

$ git checkout -b bugFix    
$ git commit    
$ git checkout master    
$ git commit    
$ git checkout bugFix    
$ git rebase master

前問が解ければ難しくはない。2つのブランチを1つに統合するmergeと違って、rebaseはブランチを付け替えるイメージ。


rampup1(2-1)

git checkout C4

やるだけ。

ところで、数年前に同じ問題解いた時は「コミットにcheckoutする」というのがよくわからなかった。当時は"git checkout 意味"とか調べても「作業ブランチを切り替える」みたいな解説しか目に入ってなかったので、「コミットってブランチの一種なの?」とか色々混乱した記憶がある。最近gitの内部仕様について勉強したということもあって今回は割とスッと理解できたけど...要はこういうこと↓

f:id:unyamahiro:20201211211629p:plain

ブランチはよく”コミットのまとまり”とか誤解されているけれど、言ってみればただのポインタで、HEADも(デフォルトではブランチを指している)ただのポインタ。checkoutっていうのはHEADの位置を移動させるコマンドというだけで、”ブランチ(上図では緑)”を指定すれば当然ブランチ(何度も言うけどただのポインタ)を指すようになるし、コミット(オブジェクトのhash、上図では青)を指定すればコミットを指すようになる。


rampup2(2-2)

$ git checkout bugFix^

HEADを動かすので、使うのはcheckout。相対位置指定を利用する。


rampup3(2-3)

$ git branch -f master C6    
$ git branch -f bugFix C0    
$ git checkout C1

ちょっとごちゃごちゃしているけど、「git branchの-fオプションで各ブランチの位置を変更し」「checkoutでHEADをずらす」だけ。


rampup4(2-4)

$ git reset local^    
$ git checkout pushed    
$ git revert pushed    

以下の記事が参考になった。
git reset 解説 - Qiita
【gitコマンド】いまさらのrevert - Qiita


rebase1(3−1)

$ git checkout bugFix    
$ git rebase master    
$ git checkout side    
$ git rebase bugFix    
$ git checkout another    
$ git rebase side    
$ git checkout master    
$ git rebase another

最初はこう解いた。実行したコマンドは8回。
しかし最短だと7回になるということだったので....このうちの2つの命令を1つに統合できるのかなと考えた。とはいえ1-2行目,3-4行目,5-6行目,7-8行目はやってることほぼ同じで、「checkoutしてrebaseする」という作業を繰り返しているだけなので、もしこの手順を1つのコマンドで実現できるならば最短は4回ということになるし...悩んだ。

まぁとりあえずcheckoutとrebaseを省略できる方法があるか調べてみた。
rebaseの公式ドキュメント見ていると、

git rebase [basebranch] [topicbranch]

みたいに引数を2つ指定すればcheckoutしなくてもいいと書いてあったので、とりあえず試してみる。

$ git rebase master bugFix    
$ git rebase bugFix side    
$ git rebase side another    
$ git rebase another master

これで解けた(最短7回とは?)


rebase2(3−2)

#模範(最短)解答ではありません
$ git rebase -i one    #UI上で順序を変更してC5削除
$ git branch -f master C5  
$ git rebase -i two    #UI上で、順序を変更
$ git branch -f master C5    
$ git branch -f one C’     
$ git branch -f two C2’’     
$ git branch -f three C2 

初めて問題を見た時、数分考えたけどまったく分からなかった。今までの応用というよりは新しいコマンドやオプションを使うんだろうなと思ったので、まずは調べてみることにした。

問題文の中で「コミットの順序を変更」という一文があったのでそれをキーワードにして検索してみる。するとrebaseの-iオプションを使えばこれを実現できることがわかったので、試しに「git rebase -i two」と入力してみたらコミットの順序変更のUIが表示された。ただ...UIの不具合か分かんないけどドラッグ&ドロップが上手くいかなかった(現場とかだとUIじゃなくてエディタで作業するみたいなので本質的な所じゃないなと思ってスルー)

ともあれ、だいたい勝手はわかったので、ブランチone,ブランチtwo,ブランチthreeとそれぞれ処理していった。先述のバグのせいで正解することはできなかったけど、上に示した手順でやればきっと問題ない(はず)。

一つ気になるのは...無駄な作業もありそうだなぁという点。なんとなく省略できる気がするけど...よくわからなかった

...一応模範解答も確認しておこうと思って、「show solution」というコマンドを実行した(これの存在をもっと早く知りたかった)

$ git checkout one    
$ git cherry-pick C4 C3 C2    
$ git checkout two    
$ git cherry-pick C5 C4 C3 C2    
$ git branch -f three C2

cherry-pickとかいう完全初見のコマンド使ってた。どうやら「必要なコミットだけを取り込む」コマンドっぽい。機会があったら別の問題で使ってみようと思った。


mixed1(4−1)

$ git checkout master    
$ git cherry-pick C4

あれ...? これ絶対3-2と出題の順番間違えてない...?


余談
ここでLearn git branchingが実は2つ(新版と旧版)があることに気付きました。
ここから新しい方で解いています。
(旧:https://k.swd.cc/learnGitBranching-ja/
 新:https://learngitbranching.js.org/?locale=ja


mixed2(4−2)

$ git rebase -i master
$ git commit --amend
$ git rebase -i master
$ git branch -f master C3’’

過去のコミットにちょっとした修正を加えたい時に「コミットの順序を入れ替えてから”commit --amend”して、その後順番をもとに戻す」という方法があるのを知った。聞いた感じ便利そうだし(コンフリクトに気をつける必要はありそうだけど)、直感的にもわかりやすかったのでスムーズに解けた。


mixed3(4−3)

$ git checkout master
$ git cherry-pick C2
$ git commit --amend
$ git cherry-pick C3

前問をcherry-pickで解くという問題。特筆事項なし。


mixed4(4−4)

$ git tag v0 c1
$ git tag v1 c2
$ git checkout c2

久々のやるだけ問題。
タグの使い方に慣れる。


mixed5(4−5)

$ git commit 

ここは、問題を解くというわけではなくて「git describe」に慣れるためにある。例えば「 "git describe C4"と入力したらどんな結果になるかな」と予め予想を立ててから実際に実行して確かめてみるときっとイメージが掴みやすいと思う。


advanced1(5−1)

$ git rebase master bugFix
$ git rebase bugFix side
$ git rebase side another
$ git rebase another master

今までの復習


advanced2(5−2)

$ git branch bugWork HEAD~^2~

HEADから目的地まで一回の命令だけで到達する問題。
当然ながら解答は上記以外にも何通りか存在する(HEAD^^2^とか)


advanced3(5−3)

$ git rebase -i one
$ git branch -f master C5
$ git rebase -i two
$ git branch -f master C5
$ git branch -f three C2
$ git branch -f two C2''
$ git branch -f one C2'

多分3-2と全く同じ問題(↑の解法はその時のもの)
これをcherry-pickで解いてみた。

$ git checkout one
$ git cherry-pick C4 C3 C2
$ git checkout two
$ git cherry-pick C5 C4 C3 C2
$ git branch -f three C2

より短くなった。


ひとまずmain編は解き終えたので、Remote編はまた今度挑戦します。とりあえず一通り解いてみて、gitのコマンドを理解したり 初学者がイメージを固めたりするのにはちょうどいい教材だと思いました。ただ、良くも悪くも直感的すぎる(現場で使用するのとはちょっと隔たりがある)ので、実際にgitに触れたりケーススタディをする等、もうワンステップがないと”使いこなす”ことは難しいと感じています。


ここからは余談ですが...
今回やったLearn git branchingでは、コミットのハッシュが"C1"みたいなすごく単純な形で表されていました。しかし実際のgitでは、(ご存知の方も多いでしょうが)コミットのハッシュは08ab3...42270みたいな、16進数の長さ40の文字列で表されています。僕が初めてgitを勉強した時は、この”ごちゃごちゃした値”が至る所に出てくるのが受け入れられず(でもなんか意外といろんな所で使われるから蔑ろにするわけにもいかず)もやもやした気持ちで勉強していました。ですがこの"ハッシュアレルギー"を、僕はgitの内部構造を勉強したおかげで克服することができました。仕様を知ることで、、ハッシュの意味等もより深く理解できるようになり、gitがどのように情報を扱っているのかもわかるようになりました。参考URLを張っておきますので気になる方はみてみてください。

参考:
画像つきでわかりやすくまとめられているブログです:
Git の仕組み (1) - こせきの技術日記


公式ドキュメント10章:
Git - 配管(Plumbing)と磁器(Porcelain)