何はともあれ
ブランチ戦略
- git flowやgithub flowを応用しながらブランチをたくさん切る。
- 大手アパレルなどでgitブランチ戦略を担当したりして来たが
fetureブランチに個人名を入れたブランチを切るのを強くオススメしている。
(feature/aaa/aaa_bob、feature/aaa/aaa_john、といった具合)
気軽にpushできるし、featureブランチはrebaseすることが多いためオリジナルを残して事故に備えやすい。
ただし後で消します。恥ずかしいし。
コミットIDの別名
-
origin:git clone http://HOST/PROJECTとしたときのURLの別名。 -
origin/BRANCH1: リモートのBRANCH1の先頭コミットIDを指す別名、とは言え最新化しないと古くなる。追跡ブランチ。git branch -aで一覧したときは「remotes/origin/BRANCH1」と表記される。 -
HEAD: 現在チェックアウトしているブランチの先頭コミットを指す、コミットIDの別名。git logの先頭でありgit resetで移動させられる。 - コミットID
ab1Cef2Hに対しての前回コミットの別名はab1Cef2H^で、この^はよく使う。diff HEAD^ HEADも頻繁に使う。
ユーザ設定の方法
-
git config user.name USERNAME(普段はgit configで設定する) git config user.email MAILADDRESSgit commit --amend --author="USERNAME <MAILADDRESS>"
設定ファイルの在り処
- リポジトリ単位 :
.git/config(git config -eで開く) - ユーザ単位 :
~/.gitconfig(git config --global -eで開く) - システム全体 :
(app-path)/etc/gitconfig(git config --system -eで開く)
各スペースの状態の確認 (手癖で使う)
-
git status: ワークツリー(赤色)とインデックス(緑色)が見れる。コミットされたもの(ローカルリポジトリ)は見れない。 -
git log: コミットされているものを見る。最も打つコマンドがstatusとlog。
何かするたびに打ってその時その時のコミットIDをコンソールに残しておく。それらのコミットIDが様々な事故から救ってくれる。git log -3などで上手くコンソールに残して行こう。 -
git diff: ワークツリーとインデックスとの比較。何をaddしようとしているかを知るときに使う。 -
git diff --cached: インデックスとHEADの比較。何をコミットしようとしているか知るときに使う。
ログの確認
git log --decorate --graph --branches --tags --remotes(おすすめ)git log --oneline --decorate --graph --branches --tags --remotesgit log --graph --date=short --decorate=short --pretty=format:'%Cgreen%h %Creset%cd %Cblue%cn %Cred%d %Creset%s'git log -3git log -5- 以前はエイリアス必須だったが、現在のGitBashは--decorateがなくても色が付くため、エイリアスなしでも運用できる。
インデックスへの追加と削除
-
git add .: カレントディレクトリ配下 (普段はgitディレクトリの頂上にいるのでこれを使う) -
git add -A: gitディレクトリ全て (ほとんど使わない) git reset FILEPATH1git reset FILEPATH1*.txt-
git reset /DIRNAME/(「/DIRNAME/*」では無反応)
リモートブランチからの取り込み方
git clone http://HOST/PROJECT -b BRANCH1-
git fetch origin: ローカルの追跡ブランチであるorigin/BRANCH1, origin/BRANCH2...を全て最新化する。 (便利だがダウンロード容量に注意) -
git fetch origin BRANCH2: origin/BRANCH2を最新化し、FETCH_HEADに入れる -
git fetch origin BRANCH3:BRANCH3: ローカルにorigin/BRANCH3とBRANCH3の2つを作る。新しくブランチを落とすときはこれ。「git fetch origin BRANCH3 && git branch BRANCH3 origin/BRANCH3」と同等。 -
git pull origin BRANCH1
有名なコマンドだが使う必要がない。pullを最新化だと思っている人も多いが、最新化はfetch。
ブランチ名を指定せず実行するとローカルの全ブランチに対して行うため危険。
FETCH_HEADの状態を確認せずにいきなりmerge(設定によってはrebase) するため予期しない変更を取り入れたりconflictを起こす恐れがある。そのためfetchしてから、log FETCH_HEADで状況を確認しつつ、merge FETCH_HEADやrebase FETCH_HEAD、reset --hard FETCH_HEADなどの選択をすればいい。 - 手元の状態を残しながらローカルブランチを最新化したい場合(
origin/BRANCH1としてではなくBRANCH1として手に入れたい場合)、一度バックアップブランチを作って手元の状態をコミットとして退避させるといい。git fetch origin BRANCH1git checkout -b BKgit add .git commit -m "MESSAGE"git checkout BRANCH1git reset --hard FETCH_HEAD
コミットはしたくないが手元の状態を一旦残したいと感じたとき
まずはコミットして一呼吸置く。コミットは reset でアンステージしてすぐ戻せる。
コミットしてコミットIDを控えておけば、その後アレコレした後でも一旦残したいと感じたときの状態に戻せる。
リモートへの反映
-
git push origin BRANCH1またはgit push origin BRANCH1:BRANCH1
(ブランチ名を指定せず実行するとローカルの全ブランチがリモートに飛ぶため非常に危険) - リモートブランチを消すときは
git push origin :BRANCH1。思っている以上にお手軽。
別のブランチからマージを避けて特定のファイルだけ持ってきたい
-
git checkout BRANCH1 -- FILEPATH1 FILEPATH2
ややこしいがcheckoutはブランチ切り替え以外に、別ブランチからファイルを持ってくる機能がある。
コミットを別のブランチに付け替えたいがマージはしたくはないとき
- 一度
mergeしてからresetしてコミットし直す。以下はBRANCH1からBRANCH2への付け替え。git checkout BRANCH2git merge BRANCH1git reset HEAD^git commit -m "MESSAGE"
- HEADではなくHEAD^^辺りのコミットを別のブランチに付け替えたい場合は
cherry-pick。使いすぎ注意。
ローカルの変更の戻し方
-
git reset --hard HEAD && git clean -df: 何もかもごっそり戻したいとき
clean -dfは空ディレクトリを消すため、残したいときは.gitkeepを置く。 - HEADを色々と移動させていてリモートブランチのHEADに戻したい場合は
git reset --hard origin/BRANCH1 - ファイル単位でリモート追跡ブランチから戻したいときは
git checkout origin/BRANCH1 -- FILEPATH1 FILEPATH2 FILEPATH3。 -
git branch -f BRANCH1 COMMITID:BRANCH1をCOMMITIDで上書き。今いるブランチ以外で使えて非常に強力。
コミットコメントやauthorやemailを直したい場合
- コミットコメントを直す場合は
git commit --amend -m "MESSAGE" - authorやemailも直すなら
git commit --amend -m "MESSAGE" --author="user.name <user.email>" - 直近の4つに対して行う場合
-
git rebase -i HEAD~4でvimを開いて、先頭を全てe(edit)にして:wqするとrebaseモードになる。 -
git commit --amend -m "MESSAGE" --author="user.name <user.email>" && git rebase --continueを4回やるとrebaseモード終了。
-
リモートの戻し方
-
git reset COMMITID && git push -f origin BRANCH1
個人用のリモートブランチならバンバン使っていい。origin/BRANCH1をfetchしていなければ--force-with-leaseで他の作業者のコミットを消してしまう危険性を下げられるらしい。全く関係のないブランチで上書きしてしまったりなどのミスが怖い。
万が一に備えて必ず元のリモートヘッドのコミットIDをどこかにバックアップしておく。コミットIDは、push前にgit logで出力してもいいし、force-push時の+ 1234abc...5678def main -> main (forced update)というログでもいい。(左側がオリジナルのコミットIDになる)
JenkinsがGitHUB上でforce-pushをして元に戻せなくなったことがある。GitHUB上ではreflogができない(元のコミットIDを見失うと元に戻せなくなる)。著名なプロジェクトだったためか、最終的にはGitHUB側が対応をして難を逃れた。
参考 : 本の虫: Jenkinsの開発者、間違えて一ヶ月前のローカルレポジトリをgit push --forceしてしまう
それを受けて(?)、GitHUBではブランチごとへのforce-pushを禁止できるようになった。(masterのみ禁止、など)
マージと競合の解消
-
git merge BRANCH2 --no-ff
--no-ffを付ける。--no-ffを付けないとコミットログにマージの痕跡が残らない。痕跡を残したくないと思っても結局conflictが起こったものはマージ跡が残り、残ったり残らなかったりカオスな状態になる。
--no-ffを付けるというルールを採用するプロジェクトもある。そのルールで良い。 -
git merge --abortでマージをやめる (mergeモード中はブランチ名が(master|MERGING)になる、それを抜ける) -
git checkout --ours -- FILEPATH1(git checkout HEAD -- FILEPATH1でいい) -
git checkout --theirs -- FILEPATH1(git checkout BRANCH1 -- FILEPATH1でいい)
一旦theirsでcheckoutして、他人のコミットをベースにして自分の修正を入れ直すことが頻繁にある。diffは基本的にはgit diff HEADで行う。 -
git merge --continueでマージの内容を決定する (エディタ等でconflictを解消した後にやる)
部分操作
-
git add -p FILE+eモード : エディタでファイルの変更の一部だけを選んでインデックスに追加する。複数の機能変更を同じファイルにしてしまった場合に利用する。意味のあるコミット単位にするための意識高い系コマンド。 -
git cherry-pick: 他ブランチをmergeしたくはないが、そのブランチの一部コミットはmergeしたい場合に利用する。mergeの痕跡が残らず、ツリーがカオスになるためおすすめできない。必要になった時点で何かがおかしい。以下が参考になる。cherry-pick 運用の地獄から這い上がった話をしよう
筆者もプロジェクトの最初からいたわけではない上に、初期メンバーも今は一人もいないので詳細はわからないが、develop ブランチを、いわゆる feature ブランチのように使ってしまったことが発端らしい。
タグをつける
git tag -a TAGNAME -m "MESSAGE"git log -1 --decorategit push origin BRANCH1 TAGNAME
タグを消す
git tag -d TAGNAMEgit push origin :TAGNAME
リポジトリのプロジェクトをcloneして、それを別のリポジトリにpushする
git clone git@github.com:XXX.git - b BRANCH1git remote remove origingit remote add origin git@github.com:YYY.gitgit push origin BRANCH1
FETCH_HEADからブランチを作成する
- 意外とよくやる操作。
git branch BRANCH1 FETCH_HEADまたはgit branch BRANCH1 origin/BRANCH1。
別リポジトリからブランチを取り込む
git fetch 別リポジトリURL BRANCH1:BRANCH1
複数行のコミットコメントでコミットする
-
-mを付けずにgit commit。viが開き、wqすればコミットされ、q!すればコミットされない。git logで他のメッセージを参照しつつコミットコメントを書けない上に、oneline指定のgit logに出てこなかったりするため使い勝手が非常に悪い。gitはちょくちょくコミットするものなので、複数行書く必要がある時点でコミット単位が粗いとも言える。長文コメントが必要なコミットになった場合は、別ブランチを切って--no-ffでマージしてそれを知らせたりタグを切ったりして、大きな分岐点になったことを残しておくと良いかも知れない。
rebaseの注意点
-
有名なコマンドだが、Git Bash上に残したコミットIDや
reflogでしか戻せずforce-pushと併用されることが多い、ちょっと危ないコマンド。ただしブランチを上手く運用するためにはrebaseが必要。rebaseを活用できる人はgitを上手く使えていると思って大丈夫。 -
戻すとき
-
git logなどで残しておいたコミットIDでreset --hard。一応reflogで上から3, 4番目辺りに出てくる「reset: checkout BRANCH1」と書かれているコミットIDにreset --hardしても戻せるが、rebaseの後に色々操作したりすると行方不明になる。
-
-
使うとき
-
ローカルで作成した自分の作業ツリーを作り直したりコミットコメントを修正したりするとき。(
rebase -i) -
ローカルで作成した自分の作業ツリーが分岐した場合にそれを一本化してリモートにpushしたいとき。
-
リモートから
fetchしてきたものを一番下にして自分のツリーをそこから始めたいとき。 -
リモートツリーを一本化する場合。force-pushと歴史改変が行われる。以下が参考になる。
3.6 Git のブランチ機能 - リベース
マージとリベースではどちらがいいのか。
お察しのとおり、単純にどちらがよいとは言い切れません。
Git は強力なツールで、歴史に対していろんな操作をすることができます。しかし、チームやプロジェクトによって、事情はそれぞれ異なります。
あなたは既に、両者の特徴を理解しています。あなたが今いる状況ではどちらがより適切なのか、それを判断するのはあなたです。
一般論として、両者のいいとこどりをしたければ、まだプッシュしていないローカルの変更だけをリベースするようにして、 歴史をきれいに保っておきましょう。プッシュ済みの変更は決してリベースしないようにすれば、問題はおきません。
-
ローカルでプロジェクトを作ったので、それを元にGitHUBのリポジトリを作りたい
- 普通この順番。まずはGitHUBにリポジトリを切ってcloneしてから、ローカルで新規プロジェクトを作成するという順番もあるにはあるが。
- まずはGitHUBに中身が空のリポジトリを作成する。(そのとき.gitignoreやLICENSEを用意してもOK)
- ローカルに作ったプロジェクトのトップディレクトリで
git initしてgitプロジェクトにする。 - そこで
git remote add origin git@github.com:xxx/yyy.gitしてGitHUBで作成したリポジトリのURLを登録する。 -
git fetch origin main:mainでGitHUBリポジトリを落として来る。 (今のGitHUBのデフォルトのブランチはmainになっている) (Git Bashもmainになっている場合は別名でアレコレすれば良し)- 「Please make sure you have the correct access rights」が出た場合 : ローカルで
ssh-keygen -t rsaしてとりあえず全部Enterを押した後、「~/.ssh/id_rsa.pub」の内容をコピーしてGitHUBのSettings -> SSH keysに登録 - 「push declined due to email privacy restrictions」が出た場合 : GitHUBのSettings->Emailからprivate設定を公開に変更 (迷惑メールがわんさか来るため危険)
- 「Please make sure you have the correct access rights」が出た場合 : ローカルで
-
git checkout mainでfetchしたブランチに移動する。 (GitHUBで作成した初期ブランチはmainブランチを想定する) - 念のため
git logでGitHUBで行ったinitial commitが存在するか確認する。 - 念のため
git statusでローカルで作成したプロジェクトが赤くなっている(ワークツリーにいる)ことを確認する。 - コミット前に
git config user.name USERNAMEとgit config user.email MAILADDRESSでAuthorを設定しておく。 -
git add .してgit commit -m "MESSAGE"してコミットログを作成する。 - 最後に
git push origin main:mainでGitHUBにpushする。 (GitHUBで作成した初期ブランチはmainブランチを想定する) (-uオプションがあるとoriginを付けなくても良くなるため初回のpushで推奨されているが、個人的には必要性を感じない) - GitHUB上で無事に
pushされていることを確認して、めでたしめでたし。
実戦で使用しているGitBash用エイリアス (オリジナル)
vim ~/.bash_profile
alias f='git fetch origin'
alias a='git add'
alias b='git branch'
alias com='git commit'
alias coma='git commit --amend'
alias ch='git checkout'
alias st='git status'
alias d='git diff'
alias dc='git diff --cached'
alias l='git log --decorate --graph'
alias push='git push'
source ~/.bash_profile