自分用のGit備忘録

gitはコマンド体系が複雑で、なかなか覚えられないので備忘録をつけます。細かすぎて毎回エントリー作るのもなんだし、でもまとまるまで待ってると忘れちゃうので、備忘録にしたいなーと思ったときに追記する形にしようかな。なので、随時追記していきます。

他にLinux(とMacの場合もあり)のコマンドとvimの備忘録も貯めてます。

ローカルに既にあるリポジトリを、githubにpushする

  1. githubリポジトリを作る。
  2. ローカルでpush出来る状態にしておく。*1
  3. $ git remote add origin git@github.com:username/foo-repos.git
  4. $ git push -u origin master

例としてusernameというユーザーが、foo-reposという名前でgithubリポジトリを作ったとする。
git push -u-uは、--set-upstreamと同じで、追跡ブランチに登録するというオプション、だと思う。多分。

svn export的なエクスポート

$ git checkout-index -a -f --prefix=path/to/export/

git-checkout-index - Copy files from the index to the working tree

このコマンドは、indexからワーキングツリーにファイルを書き出すためのものらしい。想像だけど、git checkout fooとかでブランチ切り替える場合に呼ばれたりする、どっちかっていうと内部コマンドじゃないだろうか。

-a --all-a付けないならファイルを指定することになる。
-f --force。エクスポート先に同名のファイルがある場合に上書きする。
--prefix エクスポートするファイルパスのプリフィックスってことなんだろうか。実質出力先。リポジトリ外に出力する場合は../path/to/export/かな?*2最後の/を付け忘れると、例えばpath/to/exportindex.phpの様なファイルが出力されてしまう。

git addを取り消す

$ git reset HEAD foo.txt

git addの取り消しと、コミット済みのファイルを除外する方法 - kanonjiの日記

前にエントリーにしてたけど、まとめたいのでここにも。

まだ1度もコミットしてないリポジトリで、git addを取り消す

$ git init
Initialized empty Git repository in /path/to/repos/.git/
$ touch foo
$ touch bar
$ git add .
$ git reset bar(($ git reset HEAD bar))
fatal: Failed to resolve 'HEAD' as a valid ref.
$ git rm --cached bar
rm 'bar'
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#	new file:   foo
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	bar

まだ1度もコミットしてない場合、HEADが存在しないのでgit resetできません。最初のコミットを忘れて作業を始めてしまい、挙句にstage*3に追加してしまった場合に、unstageする方法です。

git rmを取り消す

$ git checkout -- foo.txt

git reset HEADかと思ったけど違いました。git rmもコミット前の状態だし、git addと同様にunstageするって事なのかなと思ったんですが。git statusしたら分かったので--がどういう意味を持つのか分かってません。

$ git status
# On branch master
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	deleted:    lib/PIE-1.0beta2/PIE.htc
#	deleted:    lib/PIE-1.0beta2/PIE.php
#	deleted:    lib/PIE-1.0beta2/PIE_uncompressed.htc
#	deleted:    lib/css3shadow.htc
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- lib

libフォルダ内にあるファイルを全部git rmしたら、libフォルダもワーキングツリーから消えちゃいました。上記の様にフォルダを指定して取り消しも出来ます。これだけで、libフォルダ内のファイルも復活します。

git diffをファイル名だけ確認する

$ git diff --name-only HEAD^ HEAD
foo.txt
bar.txt
$ git diff --name-status HEAD^ HEAD
A       foo.txt
A       bar.txt

ブランチを作ってgithubなどのリモートリポジトリにブランチを追加する

$ git branch -r
  origin/HEAD -> origin/master
  origin/master
$ git branch
* master

$ git branch foo
$ git branch
* master
  foo
$ git checkout foo
Switched to branch 'foo'
$ git push origin foo
To git@github.com:kanonji/example.git
 * [new branch]      foo -> foo

$ git branch -r
  origin/HEAD -> origin/master
  origin/master
  origin/foo

未pushなコミットがあるかを確認する

$ git diff origin..HEAD

未pullがなければ、これで何か出力されれば未pushあり。未pullがある場合は、git loggit log originを見比べるくらいしか思いつかない。

$ git diff origin/foo..HEAD

ブランチで作業中ならブランチ名を忘れずに。

if [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
head="$(git rev-parse HEAD)"
for x in $(git rev-parse --remotes)
do
if [ "$head" = "$x" ]; then
return 0
fi
done
echo " NOT PUSHED"
fi
return 0

pushし忘れないようにプロンプトに表示するようにした - ぱせらんメモ

こんな感じでやってる人もいるみたいだけど、これ未pullがある場合もちゃんと動作するのかな?

あれ?普通にgit statusでよかった?
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#

追記だけど、なんか普通に教えてもらえる。以前は出なかったのか、条件によって出たり出なかったりなのか、自分が見落としていたのかわからないけど。git statusで分かるなら、楽でいいな。

git cherry
$ git cherry
+ b31cddf2154674eb0f51e85feea4df40fe46c009
+ 68c2276035a48e274d98731e9dd7336b6da67afd
+ 340c4e29d32737aedeb17fd0d464bfb8c3fb7a1e

追跡ブランチに未マージなコミットを確認するgit cherryでもよさそう。

追跡ブランチを設定しつつブランチを作成する

$ git checkout -t origin/foo

リモートにfooブランチが既にある場合に、ローカルにfooブランチを作るorigin/fooが追跡ブランチとして設定されます。

既に作成済みのブランチに、追跡ブランチ*4を設定する

$ git checkout -t origin/foo
fatal: git checkout: branch foo already exists
$ git branch --set-upstream foo origin/foo
Branch foo set up to track remote branch foo from origin.

既にローカルにfooブランチがある場合は、-tオプションは使えない。--set-upstreamオプションを使うけど、ローカルのブランチ名も指定する必要があります。

$ git config -l
省略
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
remote.origin.url=git@github.com:kanonji/example.git
branch.master.remote=origin
branch.master.merge=refs/heads/master
branch.foo.remote=origin
branch.foo.merge=refs/heads/foo

ちなみに、追跡ブランチが設定出来た場合、git config -lで確認できます。この例の場合、下から2行分がそうです。

ブランチを切った時から今までのdiffやlogを見る

$ git diff foo...bar
$ git log foo...bar

2つのブランチを...でつなぐと、fooとbarの分岐点からbarの最新までという指定になります。分岐点とはつまりブランチを切った時です。ただし、マージしちゃったら、マージした時点からbarの最新までとなります。ブランチを切ってからマージまでは、どうやって見るんだろう?

$ git diff foo..bar
$ git diff foo bar

紛らわしいのが..でつなぐ場合。上記2つは同じ動きをします。

リポジトリを俯瞰したい時のgit log

$ git log --decorate --graph --name-status --oneline

--decorateでブランチやタグがどこを指しているかが分かり、--graphでブランチの分岐とマージの線が見えて、--name-statusで変化のあったファイル名が分かり、--onelineで一覧性を上げてます。

なお、この画像はGitHub - yandod/candycane: a port of Redmine to CakePHP from Ruby on Railsリポジトリをサンプルにしました。

githubからcloneしたリポジトリのリモートブランチをローカルに取り出す

取り出して、追跡ブランチとして設定する。

方法1
$ git checkout --track origin/foo
Branch foo set up to track remote branch foo from origin.
Switched to a new branch 'foo'
方法2
$ git checkout -b foo origin/foo
Branch foo set up to track remote branch foo from origin.
Switched to a new branch 'foo'

こっちだとブランチ名をリモートとローカルで違うものに出来る。

補足
$ git clone https://github.com/kanonji/example.git
$ git branch -a
* master
  remote/origin/foo
  remote/origin/master

つい忘れがちだけど、clone直後はローカルにはmasterしかない状態です。さらに、リモートブランチを持ってくるのにcheckoutというのは、ちょっと直感的じゃないので、たまに忘れると思い出せなかったり。

別ブランチや過去のコミット時のファイルを見る

$ git show HEAD^:path/to/file.txt | vim -R -
$ cd path/to/
$ git show HEAD^:./foo.txt | vim -R -

:で区切る。ファイルパスはgitのワーキングコピーのrootから書く。もしカレントディレクトリが移動してたら、そこにファイルがあってもgit show HEAD^:foo.txtとは書けない。

無名ブランチを作る

$ git checkout --detach

これで現在のHEADから枝分かれした、無名ブランチを作成できます。

$ git checkout -b new_branch_name

もし、無名ブランチに何かコミットをして、このブランチを残しておきたくなったら、これで名前のあるブランチになります。

git stash pop時にコンフリクトした場合

#コンフリクトを解消してワーキングツリーが良い状態になったら
$ git stash drop

git stash popはコンフリクトしなければ取り出したstashを削除するけど、コンフリクトしたら消えない。なので自分で削除する。
※複数のstashがある場合は、どれを消すかちゃんと指定する。

状況の例
$ git stash pop
Auto-merging foo.js
CONFLICT (content): Merge conflict in foo.js
$ git status
# On branch dev
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   bar.js
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      foo.js
#
$ git stash list
stash@{0}: WIP on dev: before rebase

こんな感じでfoo.jsがコンフリクトした場合、取り出したstashが残ってます。で、解消したらどうすればいいのか迷いました。git rebaseならgit rebase --continueとかして先に進めるけど、git stash popでコンフリクトしたら、ちゃんと解消できたら自分でstashを削除しないと。

解消中にワーキングツリーがおかしくなったら
$ git reset --hard HEAD
$ git stash pop

いったんresetしてからもう一度stashを取り出す。後述するけど、この為にコンフリクト時は取り出したstashが消えないんだとか。

補足

これは意図した通りの挙動。conflict解決がうまくできなくてワークツリーの状態をぐちゃぐちゃにしてしまったあとで、reset --hardしてpopしなおせば初めから落ち着いてやりなおせる、というのが目的。きれいに当たったらもう必要ないから捨てる、というのがpopですが、これはもともとナマケモノ向きのオプションで、applyしてそれで結果が良ければdropする、という癖をつけた方が気持ちがいい、というユーザも多いかと思います。

http://d.hatena.ne.jp/mkouhei/20091124/1259074756

似たような事ではまってる人のエントリーに、Gitのメンテナの方がコメントしてました。読んでのとおりだけど、git stash popは手抜きをするためのコマンドなんだとか。
ステップバイステップで進めるなら、取り出したstashを消さないgit stash applyで適用して、問題ない事を確認してからgit stash dropするものらしい。

ローカルにあるbranchの追跡ブランチを確認する

$ git branch -vv

自分のブランチがどこのブランチを追跡ブランチ*5にしてるか、今まではgit config -lでconfig全部出してbranch.ブランチ名.remoteとかbranch.ブランチ名.mergeという項目を探してた。ブランチが増えてくると分けがわからないので、別の方法を探してたけどやっと見つけました。

コミットコメントは変えずにamendする

$ git commit --amend -C HEAD

コミットしてからtypoに気が付くとか、稀によくあるけど、そういうのを直す場合コミットコメントは変わらないのにいちいちGIT_EDITORが起動してきて面倒だったけど、これでコミットコメントを変えずにコミットできる。HEAD^とかリビジョンハッシュを指定すれば、そのコミットコメントを使えるけど、間違えやすいしHEADでしか遣わないと思う。

1個前にいたブランチを指定する

$ git checkout foo
$ git checkout bar
$ git checkout -   #barブランチに居て、1個前のブランチ。つまりfooを指定したと同じ。

以後、git checkout -を繰り返すとfooとbarブランチを交互にチェックアウトすることになる。

$ git checkout -b topic
#コード書いてコミット
$ git checkout master
$ git merge -  #$git merge topic同じ

マージする時に便利。

$ git diff -..HEAD
$ git rebase -

残念ながらdiffやrebaseには使えない様子。

git grep

$ find . -type f | xargs grep foo
$ git grep foo

findgrepでプロジェクト内の全文検索してたけど、git grepの方が楽だった。こっちなら.gitフォルダ内が検索される事もないし。

ちょっと勘違いしてたけど、git grepでもプロジェクト全域をgrep対象にするには、プロジェクトの一番上の階層に、カレントディレクトリを移動してから使わないとダメみたい。カレントがどこでもオプションで、ワーキングツリー全体からgrepとか出来たら楽なのになぁ。

[color]
    status = auto
    diff = auto
    branch = auto
    grep = auto
[grep]
    lineNumber = true

関係ないのも混じってるけど~/.gitconfigに書いておくと、マッチした所がハイライトされるのと、常に-nオプション付きな挙動になって便利です。

git grepの結果を置換する

$ git grep -l foo | xargs perl -pi -e 's/foo/bar/g'
$ git grep -l foo | xargs sed -i -e 's/foo/bar/g'

正直ちょっとめんどくさい。sedの他にperlのが書いてあるのは、いま良く使っている環境がCygwinで、sedを使うとfooをbarに置換するだけで、全改行コードを変更されてしまうから。原因を調べるのも面倒なので諦めてperl
-iオプションが無いと、置換結果が標準出力に出る。-iオプション付けると、grep元のファイルを上書きしてくれる。-i bakで、置換前をバックアップしてくれるけど、gitの管理下ならむしろ邪魔なので指定しない・・・んだけどperl-iオプションの拡張子を省略すると.bakが勝手に生成されます。消したりgit clean -fするのめんどくさい・・・。

http://www.itmedia.co.jp/help/tips/linux/l0538.html
ついでに貼っとく。

リモートのタグの削除

$ git tag -d foo                 #ローカルのタグを消す。
$ git push origin :refs/tags/foo #リモートのタグを消す。

間違えたタグを付けた上にgit push --tagsまでしちゃった場合。ブランチの場合同様にコロンを使う。

No newline at end of file

git diffすると、ファイルの最終行で同じ内容が削除され追加された事になるような±と「No newline at end of file」というメッセージが出る事がある。Vimは最終行に改行を付けるエディタなので、最終行に改行がないファイルをVimで編集すると、これが出ます。
ファイル末尾に改行を入れるのがPOSIXの流儀と書いてるとこもあったので、Vimに限らずかもしれない。

リモートリポジトリの情報を確認する

$ git remote show origin
* remote origin
  Fetch URL: git@github.com:kanonji/link2player.git
  Push  URL: git@github.com:kanonji/link2player.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

remote毎に詳細な情報が確認できます。例に使ったリポジトリはmasterブランチしかないし、面白くもないけど、雰囲気はわかるかなと。追跡ブランチも確認できます。

とあるファイルを追加したコミットを探し出す

$ git log --name-status --diff-filter=A foo/bar.js
$ git log --name-status --diff-filter=A -- foo/bar.js #削除済みで、チェックアウトしてるHEADだとgitの管理下に無い場合

--diff-filter=Aで追加(Add)のみ絞り込めて、ファイルを1個だけ指定すれば、そのファイルが追加されたコミットが出てきます。同名で2度Addしている事は、あまり無いはず。--name-statusは、このコマンドの結果で「A foo/bar.js」と出てしっくりくるだけで必須ではない。

ちなみに--diff-filter=Dなら削除コミットを、--diff-filter=Rならリネームしたコミットを探せます。

過去のコミットにタグをつける

$ git tag 1.0 6397111a3149ab7fb1ed0f84b3231a25cb1ddc2a

作り捨てのつもりが、1度出してから修正重ねることになって、一応1.0を付けたくなった場合とか。単にリビジョンハッシュ指定すればいいだけだけど。

git submoduleの使い方

submoduleの追加
$ git submodule add git@github.com:kanonji/test.git path/to/add/test
$ git commit -am 'add test as submodule'

git submodule add.gitsubmoduleファイルに設定を追記します。

submoduleを含むリポジトリをcloneした場合
$ git@github.com:kanonji/test.git
$ cd test
$ git submodule init
$ git submodule update

git submodule initにより.gitsubmoduleの設定を元に.git/configにsubmoduleの設定が追記されます。
git submodule update.git/configの設定を元に、まだcloneしてないsubmoduleをcloneしたり、指定したリビジョンに合わせたりします。
.git/configにはsubmoduleのチェックアウトしたいリビジョンハッシュが記録されていてgit submodule updateはそのリビジョンでcloneします。この辺やった事ないから想像だけど、submoduleがチェックアウト済みだけど.git/configにあるリビジョンハッシュと食い違っている場合、多分.git/configに書かれたリビジョンをチェックアウトして、合わせてくれるんだと思う。

submoduleの削除
# .gitsubmoduleから消したいsubmoduleの設定3行を削除
[submodule "path/to/add/test"]
path = path/to/add/test
url = git@github.com:kanonji/test.git

# .git/configから消したいsubmoduleの設定2行を削除
[submodule "path/to/add/test"]
url = git@github.com:kanonji/test.git

$ git rm --cached path/to/add/test #最後にスラッシュを付けない
$ git commit -am 'remove test submodule'
$ rm -fR path/to/add/test #Untracking状態でファイルは残っているので、不要なら消す

何故か削除はコマンドが用意されてなくgit submodule addgit submodule initがやった設定を、手作業で消す必要があります。

submoduleの更新、新しい変更を取り込んだり、別のブランチに切り替えたり
$ cd path/to/add/test
$ git pull or $ git checkout dev など
$ cd ../../..
$ git add path/to/add/test
$ git commit -m 'update test submodule'

submoduleを普通に操作してから、本体でそれをコミットする流れ。

タブ・スペースの変更を無視する

$ git diff -b
$ git diff --ignore-space-change 

ちなみにdiff -bも同じようにタブ・スペースの変更を無視します。

変更個所の周辺だけじゃなくgit diff, git showでファイル全体を表示する

$ git diff -U9999 HEAD^
$ git show -U9999 0c3e708

ちょっと無理矢理感あるけど、これでファイル全体が見れます。変更個所はちゃんとdiffが出るので、変更個所付近だけ見ても内容がわからない場合に便利。githubでも同じ様な事が出来たらいいのに。

.gitディレクトリがでか過ぎて困る時にガベージコレクトする

$ du -hxd 1
266M	./.git
 64K	./Config
 16K	./Console
 48K	./Controller
[snip]
273M	.

$ git count-objects
3765 objects, 252984 kilobytes
$ git gc --auto #何も起こらず
$ git gc --aggressive
Counting objects: 1095, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (1068/1068), done.
Writing objects: 100% (1095/1095), done.
Total 1095 (delta 690), reused 0 (delta 0)
$ git count-objects
1 objects, 4 kilobytes

$ du -hxd 1
 19M	./.git
 64K	./Config
 16K	./Console
 48K	./Controller
[snip]
 27M	.

`git gc --auto`は裏でも断続的に実行されているもので、あえてそれと同じものを起動する。その際、ガベージコレクトの必要がないと判断されたら何もせずに終了する。
`git gc --aggressive`は、英単語通り積極的にガベージコレクトを行う。量によっては時間もかかる。

間違ったrebaseをして戻すだとか、破棄したコミットやっぱり取り出したいって場合、GCが動いてしまうと無くなっている可能性があります。

untrackedファイルも含めてstashに入れる

$ git stash save -u

変更のあったファイルを一覧にする

$ git diff --stat 6fce80c..HEAD
 .gitmodules                               |   3 ++
 images/foo.jpg                          | Bin 100440 -> 0 bytes
css/single.css                             |  83 ++++++++++++++++++++++++++++++++++++
function.php                               |  20 +++++++++
images/banner.jpg                     | Bin 0 -> 100440 bytes
js/single.js                                  |  10 +++++
single.php                                   | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 239 insertions(+)
$ git diff --name-status  6fce80c..HEAD
A       .gitmodules
D       images/foo.jpg
A       css/single.css
M       function.php
A       images/banner.jpg
A       js/single.js
A       single.php

git logだとコミット毎に区切って並んで、まとめてみれないので、git diffを使う。

$ git diff --name-only  6fce80c..HEAD
.gitmodules
images/foo.jpg
css/single.css
function.php
images/banner.jpg
js/single.js
single.php

--name-onlyが余計なものが無くていいんだけど、削除と追加・変更の区別がつかないのがちょっと困る。

git pull --rebaseを手動でやる

$ git fetch
$ git rebase origin/master

多分合ってると思う。

git logで日付やコミット名でフィルタリング

$ git log --since 2012-01-01 --until 2012-12-31
$ git log --after 2012-01-01 --before 2012-12-31 # since, untilと同じ
$ git log --author foo       #正規表現が使えるらしい
$ git log --committer foo #正規表現が使えるらしい

author と committer は何が違うのか気になる方もいるでしょう。author とはその作業をもともと行った人、committer とはその作業を適用した人のことを指します。あなたがとあるプロジェクトにパッチを送り、コアメンバーのだれかがそのパッチを適用したとしましょう。この場合、両方がクレジットされます (あなたが author、コアメンバーが committer です)。この区別については 第 5 章 でもう少し詳しく説明します。

http://git-scm.com/book/ja/Git-%E3%81%AE%E5%9F%BA%E6%9C%AC-%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E5%B1%A5%E6%AD%B4%E3%81%AE%E9%96%B2%E8%A6%A7

*1:未コミットのものをコミットしておくなど

*2:絶対パスが使えるかは知らない。

*3:index

*4:tracking branch / upstream branch

*5:upstreamとも