自分用のGit備忘録
gitはコマンド体系が複雑で、なかなか覚えられないので備忘録をつけます。細かすぎて毎回エントリー作るのもなんだし、でもまとまるまで待ってると忘れちゃうので、備忘録にしたいなーと思ったときに追記する形にしようかな。なので、随時追記していきます。
他にLinux(とMacの場合もあり)のコマンドとvimの備忘録も貯めてます。
ローカルに既にあるリポジトリを、githubにpushする
- githubでリポジトリを作る。
- ローカルでpush出来る状態にしておく。*1
$ git remote add origin git@github.com:username/foo-repos.git
$ 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 log
とgit log origin
を見比べるくらいしか思いつかない。
$ git diff origin/foo..HEAD
ブランチで作業中ならブランチ名を忘れずに。
if [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
pushし忘れないようにプロンプトに表示するようにした - ぱせらんメモ
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
こんな感じでやってる人もいるみたいだけど、これ未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
find
とgrep
でプロジェクト内の全文検索してたけど、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 add
やgit 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