gitまとめ

いろんな異なるバージョンのプログラムを管理したい、というわけで、gitを使うことにしました。cvssvnやhgやgitはいままで長いこと使ってきていたけども、ブランチをたくさん作るスタイルで使ったことはなかったもので。案の定つまずきまくりなので自分が後で参照するためにここにまとめていくことにする。入門Gitが手元にないので調べられない…あの本にこういう問題にぶち当たったときの解決方法が書いてあった気がするのに〜

ブランチfoo上で、本来ブランチbarで編集するべきa.pyを編集してしまう

ブランチfoo上で、本来ブランチbarで編集するべきa.pyを編集してしまい、ブランチbarをcheckoutしてそこで反映しようにもfooとbarでa.pyの内容が違うため「error: You have local changes to 'a.py'; cannot switch branches.」になる。

git diffの結果をリダイレクトしてtmp.patchに取っておいてgit checkout fooでローカルの修正を消し、git checkout barしてからgit am tmp.patchしようとしたら「Patch format detection failed.」うぎゃあ。

幸い修正箇所は1行だったので手で直したけどどうするべきだったのか。

新しく作ったブランチに移動し忘れてコミット

今のmasterのa.pyはそのままにしてa.pyの改良版を実験的に作ろうと思い立ち、git branch fooして編集、git add a.py、 git commit。さてgit checkout fooをしわすれたままコミットしたのでmasterのa.pyが実験版、fooのHEADのa.pyが変更前版という逆転した状態になってしまった。

とりあえずmasterのほうではgit reset HEAD^でよさそうだよね。このコミットをfooの方にも適用してからmasterを巻き戻せばいいよね。checkoutだとブランチが変わっちゃうからmergeかなぁ。

$ git checkout foo
Switched to branch 'foo'
$ git merge master
Updating 8e35e6b..56dbe31
Fast-forward
 a.py |   31 ++++++++++++++++++++++++++++---
 1 files changed, 28 insertions(+), 3 deletions(-)

うむ、正解のようだ。でcheckout masterしてreset HEAD^だね。

$ git checkout master
Switched to branch 'master'
$ git reset HEAD^
Unstaged changes after reset:
M	a.py

ほう、ローカルの変更は残ってしまうのか。reset --hardにするべきだったか?

$ git reset --hard master
HEAD is now at 8e35e6b hogehoge

多分あってる。

残念、今回は「間違ったブランチでコミットした上に、気づかずにpush」までやってたのでこれではダメでした。masterをreset HEAD^した状態は「ローカルのリポジトリよりリモートのリポジトリのほうが1つ新しいコミットがある」なのでpushしようとしたときに拒否されてしまう。「To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again.」と言われるが…マージしてからrevertで逆向きのコミットを作るか。git pull→git revert HEAD→git push。うまく行ったように思える。

作業中に他のブランチに移りたくなる

あとでamendできるのを知っていたのでcommitした。実はそれが上の「間違ったブランチにコミット」だったので大騒ぎだったが…まあコミットしてあればよっぽどひどいミスをしなきゃ消えてなくなることはないので心理的に安心。

異なるブランチでdiff

masterのHEADとbranchxのHEADをdiffしたい時ってどうするの?

git diff [--options] [--] [...]
This form is to view the changes you have in your working tree relative to the named . You can use HEAD to compare it with the latest commit, or a branch name to compare with the tip of a different branch.

というわけでmasterブランチの上でgit diff branchxすればOK

ワーキングコピー上のいらない変更を捨てたい

svn revertみたいな。git checkout HEADすればいいかなと思ったが、それじゃ「M foo.py」って言われるのでgit checkout --force HEAD した。複数ファイルをいちいち指定したくないからHEADを指定してみたのだけど、正解はgit checkout -- のようだ--はfilenameと同名のブランチがなければ省略できるし、ファイル名はディレクトリを指定することもできる。というわけで僕のケースの正解はgit checkout .だな。

cherry-pickしたらコンフリクトした

diffで編集されている範囲には衝突がなくても、変更されている範囲のすぐとなりに変更があったためコンフリクトが起きてしまった。落ち着いてコンフリクトを直してから指示通りgit add してgit commit -c すればよい、はずだが僕は間違えてgit add .してしまい、追加してしまったいらないファイルをいちいち戻すのが面倒でgit reset HEADしたためコンフリクトとかすっかり忘れて修正済みの状態になってしまった。commit -cしてもいいのかな。

-C , --reuse-message= Take an existing commit object, and reuse the log message and the authorship information (including the timestamp) when creating the commit.
-c , --reedit-message= Like -C, but with -c the editor is invoked, so that the user can further edit the commit message.

なんだ、そんだけか。

コンフリクトしてよくわからないことになったので戻したい

git reset HEADでポインタを動かさずにコンフリクトなどの状態をリセットして、checkout .でHEADの状態を復元したらいいと思ってるけど正解かな?

コミット時のメッセージに

# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)

と書いてあるのでstageした内容などをなかったことにするのにreset HEADを使うのは普通らしい。

ローカルで新しく作ったブランチをgithubにpushしようとしてエラー

git push -u origin masterしてうまく行ったので気を良くして新しいブランチfooを作って作業してからgit push -u origin fooしてエラー「error: src refspec forth-definable does not match any」

-uを外したらOKだと思ったが、原因はそれじゃなくてブランチ名typoだった。補完を入れましょう。

今から自分がコミットしている変更差分を見たい

git diffのmanを読みましょう。git diff --cached で「次にコミットされる予定のものと指定したコミットの差分」であり、省略時にはHEADになる。よってdiff --cachedでOK

小ネタ

gitなのに.hgignoreを作ってしまった

しかもうまく動かないと思ったら.gitingoreだった

ブランチ名typoとか違うブランチで作業しちゃうとかはzsh使えば解決だよ!と言われてhttps://github.com/ymotongpoo/dotfiles/blob/develop/.zshrc の51~96行目をもらってきて「autoload -U compinit && compinit」(zshで究極のオペレーションを:第5回)したらとても快適になった。