LoginSignup
9
9

More than 5 years have passed since last update.

gitの使用方法

Posted at

gitを使用するようになってから、数年。自分なりの使用方法が定着してから、結構経つが、最近、その使用方法の改善について、停滞気味。
gitは、あることをするのに色々な手段が取れる。だから、より簡単で安全で効率の良い方法をいつも探している。
それ故に、初心者やSubversionからの移行者が非常に取っつきにくく、調べても、基本的な手法すら記載されていない。調べても出てこないので、それが正しいのか、どうすると効率が良いのかなど、私も未だ悩むことも多い。
大体、gitの使用方法を調べていると、下記の2段階で出てくる。

  1. gitの基本的なコマンド
  2. 運用方法(GitHubフローを使うとか)

しかし、Subverionと違い、色々な方法が取れるgitであるからこそ、1と2の間にあたるユーザー操作の方法が無限に存在するように思っている。
私がSubversionから移行するときも、その部分には悩まされたし、プロジェクトメンバーに教えていても、コマンドは似通った部分があるから何となく分かるけど、何が正解か分からない、ベストプラクティスがないどころか、基本的な手法すら出てこないとよく聞く。
状況によって、ベストな方法があるから、とも言えるかもしれないが、それでもとりあえずは操作の指標は欲しいところ。
つまり、実際に書いたコードを上げたり、他人のコードを取り込んだりする時、どのやり方(pull、fetch & merge、rebase)をすれば正解か、実際にやった時にどんな挙動になるかもよくわからないし、それがあってるかどうかも分からないから、困ってるということ。

まずは、ブランチ

Subversionからgitへ移行する際、ブランチの取り扱いの差異に最初は戸惑った。Subversionのブランチは運用手法だが、gitでは、システムだ。
gitのブランチは他人へ影響が無いから、とにかく簡単にブランチが切れる。影響が無いし、簡単に戻せる。
gitは、ブランチを操作するものだから、まずはブランチを理解する必要がある。

二種類のブランチ

ともかく、まず、ローカルリポジトリ内には、リモートブランチとローカルのブランチがあることを理解すること。
コマンドで見てみると、こんな感じになってる。

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

これは、全部ローカル内にある。先頭にremotesと付いてるのが、外部のリポジトリと連携をとるためのもので、外から持ってきたものが入ってる。そして外に出す際に一緒に更新される。どの順番で更新されるのかは知らない。要は、外部のコピーもローカルに入っていると思っておけば良い。
詳しくは、下記を見ていただいた方が良いかと思う。
http://qiita.com/uasi/items/69368c17c79e99aaddbf

操作方法

で、それぞれの操作方法は下記の通り。remotesの中身は、勝手に更新されないので、fetchで手動更新する。ある程度操作したことがあるなら、これで何となくデータの流れがわかってくると思う。

操作内容 ローカルブランチ リモートブランチ
作成 checkout -b push
更新(自分の) commit push
更新(取り込み) merge fetch
削除 branch -d push --delete

自動連係

push、pullする時はupstreamが設定してあれば、push先は指定しなくても良い。masterしか使用していないと、最初に自動設定されただけで触らないので分からないと思うが、要はローカルブランチとリモートブランチの関連付けを行っておく。ブランチ名は同じである必要はないので、自分で好きなように関連付けられる。
下記のようになっている場合、hogeは連携先がないので、pullやpushする際に宛先を指定する必要がある。宛先は、origin/hogeにも、origin/masterにもできる。masterをorigin/hogeに関連づけることもできる。

$ git branch -vv
* master aaaaaa [origin/master] test
  hoge   aaaaaa test

ローカルにあるものは自分の自由にしてよい

ローカルにあるものは、壊してしまっても戻してしまえば良い。使わないのなら、ローカルのmasterブランチも消しても全く問題ない。所詮、masterと名のついた只のブランチでしかないから。
ただし、remotes/のブランチは、コマンド操作すると相手側と連動されるので、ローカルから消したくなったらgit remote remove originで、連携を外すか、.gitディレクトリごと消そうw

まずはブランチを理解すること

  • ブランチ同士にどのような関連性があるか(ローカルとremotes)
  • どうやって関連づいているか(upstreamの設定)
  • どのようなデータフローになっているか(fetch、merge、pushした時にどこが更新されるか)

gitを操作しようとすると、このブランチが理解できずに行き詰まる。使用するだけなら、それほど難しくないはずなので、あんまり深く考えず、感じてみよう。

私が使用しているgit操作手順

で、色々彷徨いつつ、自分なりに確立した方法が下記になる。

  1. ローカルリポジトリを共有リポジトリからcloneする(git clone URL
  2. 作業ブランチを切る(git checkout -b feature_featurename_myname
  3. 作業する
  4. 一日の終わり、または気が向いたときにcommitする(git add . && git commit -m 'f'
  5. 一区切りついたら、commitを整理する(git rebase -i HEAD^^^
  6. リモートのcommitを取り込む(git remote update && git fetch -p && git rebase origin/master
    1. conflictが起きたら、解消する(git mergetool
    2. よくわからなくなったら、戻す(git rebase --abort
    3. merge失敗した時用にバックアップを取っておく(git checkout -b backup
    4. マージできたら次を処理する(git rebase --continue
    5. 全体のdiffを見て、きちんとmergeできたか判断する(git diff backup feature_featurename_myname
  7. バックアップも兼ねて、リモートへブランチをpushする(git push -f origin feature_featurename_myname
  8. 3 - 7 を繰り返す
  9. 機能が完成したら、masterにマージする(git checkout master && git fetch -p && git pull && git merge --no-ff feature_featurename_myname
  10. マージしたmasterをremoteに送る(git push origin master
  11. 不要になったブランチを消す(git branch -d feature_featurename_myname && git push --delete origin feature_featurename_myname

プロジェクトメンバーのgit練度の兼ね合いもあって、GitHubフローなどは使用していないが、とりあえず、この操作内容の意味が分かるのであれば、運用方法が何になっても困らないと思う。
ちょっと補足

  • git remote updateは、リモートが複数ある時だけで良い
  • ローカルmasterの更新は、pullでも、mergeでも良い
  • 変更点取り込みのタイミングは、固定ではない、commitしてすぐにしておいても良い。

利点

この方法だと、commitが綺麗になる。masterの取り込みをrebaseで行って(git rebase origin/master)、ブランチの適用をnon fast-forward(git merge --no-ff)で行うと、他の人の変更点を取り込んだ後で、自分がまとめて変更点を適用したことになる。
時系列的には違うが、コードの正当性としては(conflictをきちんと解消していれば)間違っていない。
また、ブランチでの作業分が、まとまってきっちり分離され、概要がmergeコメントに載っているので、非常に分かりやすい。
git_commit.gif

欠点

git rebaseでconflictが起きた場合、解消が難しくなることがある。rebaseの間、ずっとconflictが起きることがあるため。

それぞれの意味

commitする

無駄にcommitしてるけど、大丈夫か。問題ない。
後にも書くが、commit時点では確定ではない。むしろ、訳の分からない変更点が大量に貯まる前に、適当な位置でcommitしておいた方が、前回作業とのdiffも取れて便利。
commitが増えてきたら、とりあえず、fixupでまとめてしまえば良い。

git rebase origin/master、git mergetool

利点にも書いたが、rebaseすると、最新の状態から、自分が変更を行ったことになる。
その作業で、conflictをきちんと解消するはずなので、それで問題ないはず。問題になったら、きちんとconflictを取り込めなかった自分が問題。それは、相手の変更点を理解していなかったと同義だ。
また、mergeで取り込むと、自分のコミットログと他人のコミットログが混ざるが、rebaseは、当たり前だが、それがない。
rebase origin/masterは、下記のようにイメージすると分かりやすい。

  1. 最新のorigin/masterの先頭に自分のcommitを順番に先頭にくっつけていく
  2. conflictが起きたら、その時点の変更ファイルをワーキングに展開して止まる
  3. conflictを解消したら、git rebase --continueすると、そこからまた、自分のcommitを順番にくっつけていく

変更ファイルを展開する時は、また、git branchを見てみると分かるが、一時ブランチで行われるので、git rebase --abortで、rebase実行直前の状態にすぐに戻せる。

git push -f origin feature_featurename_myname

push -fはいけないんじゃ。。と思うかもしれないが、自分専用のブランチで問題なんて起きるの?
間違って、masterにpush -fする可能性が問題かもね。。まあ、それをやらかすのは、masterの過去のcommitを変更するような使用方法に問題があるからだけどね。普通は無いね。

git pull && git merge --no-ff feature_featurename_myname

masterは、リモートと同期をとっているだけなので、作業ブランチと違いgit pullしてもコミットログが崩れない。
利点にも書いたが、--no-ffをつけて、ブランチを取り込むことで、自分の作業分のみを分離したログを出せて、見やすくなる。

Subversionとの主な違い

  • とりあえず、commitしまくる。途中でもする。Subversionのようにcommitを神聖視するな。(masterにpushしなければ)何も問題にならない。日常のcommitは、らくがき程度の扱いなので、pushする前に、他人に見せるようにまとめれば良い。
  • ローカルのリポジトリや自分専用のブランチは、他人が触る余地がないので、都合が良いように改変しまくれ。誰も見てないんだから(masterにpushしなければ)何も問題ない。そして、ブランチも切りまくれ。困ったら切れ、不安なら切れ、ブランチは、超低コストのcp -rvimdiffだ。ブランチを切った(所謂cp -rした)先は、壊しても問題ない。元のブランチを使うだけだ。うまくいったらmergeで取り込む(vimdiff)なり、そっちをメイン作業スペースにするだけだ。

Subversionからすると、commitやブランチの扱いがだいぶ違うと思う。Subversionからの認識を改めるべき。
Subversionとgitの対応コマンドを調べるとよく見つかる。確かにその通り間違っていないのだが、そのまま使用しようとするとgitの利点を全く生かせない。それどころか、無理にSubversionに合わせようとするせいで混乱が生じて、余計に難しくなってしまう。
特に、commit、svn commit = git commit && git pushとは思うな、svn commit = git push origin masterと思うべき。ここでのorigin masterは重要。
git commitは、確定された作業内容ではなく、個人の作業ログ程度の扱いで良い。
そして、Subversionでは、手動で行っていた操作をgitで簡単に行える。
例えば、Subversionでは、mergeするとワーキングツリーが壊れるから。。とかで、ファイルやワーキングツリー丸ごと、一時的な退避を行っていたが、それもgitでは、reflogで大体戻せるし、バックアップ用ブランチを切ってしまえば、それらを手動でやる必要すらない。
対応表に表すとこんな感じだと思えば良い。とにかく、目的は、大体gitで解決できるはず。

Subversion Git 意味
svn commit git push origin master 変更確定
git commit 今日の作業、気が向いた時の変更ログ、変更確定の準備
git checkout -b feature 作業スペース確保
cp -r ../project{,.bak} git checkout -b backup バックアップ、変更テスト用スペース
cp file{,.bak} git commit -m 'f' 確度の高い変更箇所を残す
cp file{,.bak} git stash 確度の低い変更点をバックアップ
svn up git rebase origin/master 変更点の取り込み

最後に

gitシステム自体が、ブランチを使用しているところが多々あるように、gitは、ブランチを操作するものである。だから、ブランチの扱いに慣れることが重要。
それと、Subversionからの移行者は、commitやブランチの認識を改める必要がある。

9
9
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
9
9