Heavy Watal

Git入門2018

2018-10-15 中央水産研究所

事前準備

OS別

新しめのUNIX系OSが好ましい。

OS共通

SSHの設定 (任意)


以下、本編。

Gitが無い世界の風景

バージョン違いのファイルがフォルダを埋め尽くす。

% ls
analysis.R
analysis2.R
analysis-20180129.R
analysis-20180129改良版.R
analysis-20180210.R
analysis-20180210バグ?.R
analysis佐藤edit.R
analysis佐藤edit田中.R
analysis完全版.R
analysis最終.R
analysis最終改.R
analysis決定版!.R
analysis真・最終.R
plot.R
plot2.R
plot最終.R
plot論文.R

ファイルの中にも、いつか使うかもしれなくて消せないコードがたくさん。

ジャンクコードまみれになって、開発効率は低下、バグ混入リスクは上昇。

オンラインストレージやバックアップ機能では不十分

でも、バージョン管理や共同作業のためのツールじゃないから…

Git and GitHub

e.g., https://github.com/tidyverse/stringr/commits/master

両者はどういう関係?

Git は分散型バージョン管理システムとして最も広く使われるオープンソース・ソフトウェア。 手元のコンピュータ上でこれを操作して、変更履歴を記録・閲覧したり送受信したりする。

GitHub はGitをより便利に使うためのオンラインサービスであり、それを運営する会社の名前でもある。 個人的なリポジトリ置き場としてはもちろんのこと、 多人数で共有・協力してプロジェクトを進めるプラットフォームとしても使える。

類似ツール

VCSは基本的にGit一択。
ホスティングサービスは、使い方や予算などに応じて選択。

GitHubの使いみち

構造と操作の概要

手元の変更を外に伝える

working directory (working tree)
手元のファイルの変更はまだリポジトリに登録されていない
git add
staging area (index)
次のコミットに含めるファイルをマークする段階
git commit
local repository
変更履歴が .git/ 内に記録されている
git push
remote repository
GitHubなど別マシンのリポジトリに反映

外部の変更を手元に取り込む

remote repository
git fetch
local repository
変更が .git/ に取り込まれたが、見えてるファイルには反映されてない
git checkout or git merge
working directory
手元のファイルが最新版に同期されている

用語

https://help.github.com/articles/github-glossary/

repository
commitの履歴を保持する拠点。 「ひとつのRパッケージ」とか「1冊の本の原稿」のような単位で作る。 git init で手元に新規作成するか、git clone でリモートから複製する。
commit
git内部でroot treeのsnapshotを指すオブジェクト。 root treeのハッシュID、著者、コメントなどの情報を持つ。 動詞としては、staging areaの情報をひとつのcommitとしてリポジトリに登録することを指す。
tree
git内部で1つのディレクトリを指すオブジェクトで、commitした時に作られる。 blobやファイル名などのメタデータに依存したハッシュIDを持ち、 その変化は親に伝播する。
blob
git内部で1つのファイルを指すオブジェクトで、add時に作られる。 ファイル名などのメタデータは持たず、 ファイルの内容にのみ依存したハッシュIDを持つ。
origin
remoteリポジトリの典型的なshortname。 clone時に自動的に追加され、 push先やfetch元を省略したときにデフォルトで使われる。 git remote -v で確認。
master
デフォルトのブランチの典型的な名前。
HEAD, @
現在checkoutしているbranch/commitを指すポインタ。 基本的にはmasterの最新commitを指していることが多い。 1つ前は HEAD^HEAD~、 2つ前は HEAD^^HEAD~~HEAD~2。 (HEAD^2merge で複数の親がある場合の2番目)

zshのEXTENDED_GLOBが有効になってる場合は HEAD^ がパターン扱いされてエラーになるので、 HEAD\^ のようにエスケープするか unsetopt NOMATCH しておいたほうがいい。

基本操作の実践

既存のリポジトリを取ってくる clone

  1. GitHub上の適当なリポジトリをひとつ選ぶ。 (e.g., https://github.com/heavywatal/clippson)
  2. 右の方の緑の “Clone or download” ボタンを押す。
  3. SSHではなくHTTPSを選択し、URLをコピー。
  4. ターミナルにコマンドを入力:
    git clone https://github.com/heavywatal/clippson.git
  5. 中身を眺めてみる:

    cd clippson/
    ls -al
    git log
    git remote -v

最新版のスナップショットだけでなく、 履歴もごっそり複製するので、 このあとはオフラインでもいろいろ操作できる。

新しいリポジトリを作る init

  1. GitHubの右上の “+” から “New repository” を選択。
  2. Repository name を例えば helloworld として “Create repository” を押す。 いくつかのファイル (README.md, LICENSE, .gitignore) をここで作ることもできるけど、今回はとりあえず空っぽのリポジトリを作る。
  3. 手元のマシンにローカルリポジトリを作る:

    mkdir helloworld
    cd helloworld/
    git init
    ls -al

    リポジトリの本体 .git/ が作成されたことを確認。

  4. 空っぽのコミットを作る:

    git status
    git commit --allow-empty -m ":beer: Create repository"
    git status
    git log

    事あるごとに git statusgit log を確認すると安心。

  5. 先程作ったリモートリポジトリを紐付けて、pushしてみる:

    git remote -v
    git remote add origin https://github.com/YOUR_NAME/helloworld.git
    git remote -v
    git push -u origin master
    git status
  6. GitHubで履歴を閲覧し、 git log と同じになってることを確認。

手元の変更をリモートに push

  1. 上で作ったリポジトリに、適当なファイルを追加:

    echo "# Hello, world!" > README.md
    cat README.md
    git status
  2. 作ったファイルをstaging areaに追加:

    git add README.md
    git status
    git diff --staged
  3. この変更をcommit:

    git commit -m ":memo: Create README.md"
    git status
    git log
    git show
  4. リモートにpush:

    git push
    git status
    git log

リモートの変更を手元に fetch

  1. 上のリポジトリでそのまま git fetch してみる。 ローカルとリモートは同じ状態なので当然何も起こらない。

  2. 練習のためGitHub上で LICENSE ファイルを作成する。

    1. GitHub上のリポジトリのトップページを開き “Create new file” ボタンを押す。
    2. ファイル名に LICENSE と入力。
    3. 右に現れる “Choose a license template” というボタンを押す。
    4. とりあえず “MIT License” を選択。
    5. YearとNameを適当に埋めて “Review and submit”。
    6. “Commit directly to the master branch” を選択して “Commit new file”
  3. その変更をローカルリポジトリに取り寄せる:

    git fetch
    git status
    git log --all
    ls -al

    リポジトリ内部 .git/origin/master は更新されたが、 working directoryにはまだ反映されていない。

  4. origin/master の内容を手元のファイルに反映する:

    git merge
    git status
    git log
    git show
    ls -al

git fetchgit merge を一気にやってくれる git pull というコマンドもあり、 普段の一人作業ではよく使う。

その他よく使うコマンド

diff

差分を表示:

# HEAD vs working (staging前のファイルが対象)
git diff

# HEAD vs index (staging済みcommit前のファイルが対象)
git diff --staged

# HEAD vs working+index (commit前の全ファイルが対象)
git diff HEAD

# 特定コミットの変更点 (diffじゃない...)
git show [revision]

rm, clean

tracking対象から外して忘れさせる(手元のファイルはそのまま):

git rm --cached <file>

.gitignore で無視されてるuntrackedファイルを消す:

git clean -fdX

無視されていないuntrackedファイルも消したい場合は小文字の -fdx (危険)。

reset

git reset <DESTINATION>HEAD の位置を戻す処理で、 オプションによってindexとworing treeもそこに合わせるように変更される。 --soft なら HEAD 移動のみ。 --mixed なら移動した HEAD にindexも合わせる。 --hard なら移動した HEAD にindexとworiking treeも合わせる。 直前の動作を取り消す用途に絞って使うのが無難:

# commit直後、それを取り消す (indexとworkingはそのまま)
git reset --soft HEAD^

# add直後、それを取り消す (workingとHEADはそのまま)
git reset --mixed HEAD

# 変更したファイルをHEADの状態に戻す (DANGEROUS!)
git reset --hard HEAD

# reset直後、それを取り消す
git reset --hard ORIG_HEAD

# divergedになってしまった手元のbranchを破棄 (DANGEROUS!)
git reset --hard origin/master

直前のcommitをちょっと修正したいだけなら git commit --amend が簡単。 それより前のを修正するには git rebase -i HEAD~3 とかで戻ってrewordやedit。

リモートにpush済みのものは改変しちゃダメ!

チーム作業

Pull Request (PR)

他人のリポジトリに貢献するためのGitHubの機能。
e.g., https://github.com/Rdatatable/data.table/pull/2807

  1. 貢献したいリポジトリをForkして自分のGitHubアカウントに追加。
  2. Forkした自分のリポジトリを手元にclone。
  3. PR用のブランチを作って、そこでソースコードを編集。
  4. コミットして、ひとまず自分のGitHubアカウントにpush。
  5. GitHub上で大元のリポジトリにPRを送る。
  6. 取り込んでもらえたら、用済みのブランチを削除。

2人1組でPRとmergeを体験

(できれば横に並んで相手の画面も見えるように)

  1. GitHubで新しいリポジトリを作成
  2. 何かtypoを含む README.md を作ってpush
  3. 相手のGitHubリポジトリでその README.md が見えることを確認
  4. 右上のForkボタンで自分のGitHubリポジトリに取り込む
  5. forkした自分のリポジトリからローカルにclone:

    git clone https://github.com/{PAWN}/PROJECT.git
    cd PROJECT/
    
  6. 大元のリポジトリにupstreamという名前をつけておく:

    git remote add upstream https://github.com/{KING}/PROJECT.git
    git remote -v
    

    ちなみに自分のリポジトリには自動的に origin という名前がついている。

  7. PR用のブランチを切って移動:

    git checkout -b fix-typo
    
  8. README.md をテキストエディタで編集して commit:

    git diff
    git commit -a -m ":memo: Fix typo in README.md"
    

    Git連携機能のあるエディタを使っている場合、 そこからdiffやcommitをやってみてもよい。 コードの追加・変更・削除による色分けの便利さも体感しよう。

  9. この間にupstreamで更新が無いかどうか確認:

    git fetch upstream
    

    もしあったら、それをデフォルトブランチ(master)越しに取り込む:

    git checkout master
    git merge upstream/master
    git push origin/master
    git checkout fix-typo
    git rebase -i master
    
  10. 自分のリポジトリにpush:

    git push origin fix-typo
    
  11. GitHub上に出現する “Compare & pull request” ボタンを押す。

  12. 差分を確認し、コメント欄を埋めて提出。

  13. 受け取ったPRを確認。必要に応じて修正を要求したり、自分で修正したり。

  14. 修正を求められたらそのブランチに続けてcommitしてまたpush。

  15. 問題が無ければmergeする。

  16. 自分のローカルリポジトリに pull (fetch+merge) する。

  17. 無事マージされたら作業ブランチを消す。

Tips

Further reading