gitプロジェクトのルートディレクトリの絶対Pathを取得
プロジェクト以下ならどこからでも実行できるコマンドなどで、ルートディレクトリからの相対 Path で決まったディレクトリやファイルを取得したい時があったりしました。
$ git rev-parse --show-toplevel
/Users/user/dirname
プロジェクト以下ならどこからでも実行できるコマンドなどで、ルートディレクトリからの相対 Path で決まったディレクトリやファイルを取得したい時があったりしました。
$ git rev-parse --show-toplevel
/Users/user/dirname
新しい参照を作る時に出た422(Unprocessable Entity)エラーは、ブランチ名をパスで見た時に親の名前でブランチを作ろうとしている為です。
既にディレクトリがある場所に同名でファイルは作れないように、参照はディレクトリの構造で管理されているので同じように参照ファイルを作ることができません。
git push origin master:parent/child
# Total 0 (delta 0), reused 0 (delta 0)
# To github.com:nju33/playground.git
# * [new branch] master -> parent/child
#
git push origin master:parent
# Total 0 (delta 0), reused 0 (delta 0)
# To github.com:nju33/playground.git
# ! [remote rejected] master -> parent (cannot lock ref 'refs/heads/parent': 'refs/heads/parent/child' exists; cannot create 'refs/heads/parent')
# error: failed to push some refs to 'git@github.com:nju33/playground.git'
ls -l .git/refs/remotes/origin
# drwxr-xr-x 3 nju33 staff 102 Aug 29 14:33 parent
echo a > .git/refs/remotes/origin/parent
# An error occurred while redirecting file '.git/refs/remotes/origin/parent'
# open: Is a directory
大抵はこれで持ってくるはずです。
git clone <remote-url>
これに、-b
オプションと保存先ディレクトリを指定してあげるだけです。
git clone -b <branch-name> <remote-url> <branch-name>
--single-branch
を指定するとリモート上のブランチなどを取ってこなくなります。
これでクローンしてくると、.git/config
ファイルが以下のようになっています。
[remote "origin"]
url = <remote-url>
fetch = +refs/heads/fix/<branch-name>:refs/remotes/origin/fix/<branch-name>
これが作業ブランチだとして、master
ブランチだけは定期的に取り込むために参照したいような場合は、このファイルのfetch
を追加します。
[remote "origin"]
url = <remote-url>
fetch = +refs/heads/<branch-name>:refs/remotes/origin/<branch-name>
fetch = +refs/heads/master:refs/remotes/origin/master
これでfetch
すれば取ってこれると思います。
$ git fetch
$ git branch -a
* <branch-name>
remotes/origin/<branch-name>
remotes/origin/master
# .git/modules/<submodule-name> を削除
git submodule deinit <submodule-name>
# <submodule-name> を消して、.gitmodulesからも情報をさく削除
git rm <submodule-name>
# (今度はdevelopブランチで)再度追加
git submodule add -b develop <submodule-name>
git rm
を先にやってしまった場合は、git submodule add -f
とすれば大丈夫です。
マークアップ系がコンフリクトを起こしてるけど、編集したのは自分じゃないし解決する自信あまりない。でもマージもできないのでCSSファイルなどを持ってきてUIを見ながら修正することもできない。
調べるとCSS関連のファイルではコンフリクトは起きてないので、これだけ持ってきちゃいたい。
for file in `git diff <比較ブランチ> --name-only | grep .css`; do git checkout develop $file; done
git diff --name-only
はファイル名だけを羅列するのでそれをgrep
でほしいファイルだけに絞り込みgit checkout <とあるブランチ> <filename>
でとあるブランチ状態のそのファイルの状態にチェックアウトfor...in
で上2つを繰り返し実行これはhub
(github/hub)に依存してます。
Macの人はbrewで入れれます。
brew intall hub
以下で開けます。
hub browse -- pull/${PULL_NUMBER}
PULL_NUMBER
は見たいプルリクのid
です。
ただ多分プルリクのid
は覚えてないことが多いのであまり使えないですが、GitHubはPULL_NUMBER
がブランチ名の場合自動でid
に変換してくれる仕様があります。つまり、こんな感じでいけます。
hub browse -- pull/${BRANCH_NAME}
よく使うものとして、今いるブランチのプルリクを開きたいと思うかもしれませんが、それはこうします。git rev-parse --abbrev-ref HEAD
は今いるブランチ名を取得しています。
hub browse -- pull/`git rev-parse --abbrev-ref HEAD`
こんな感じで登録すると便利か…も?
function openpull () {
current_branch_name=`git rev-parse --abbrev-ref HEAD`
hub browse -- pull/${1:-$current_branch_name}
}
alias openpull=openpull
openpull
# 現在のブランチで開く
openpull foo
# fooブランチで開く
今master
ブランチでfoo
という Submodule は aaaaaa
というコミットが最新です。そしてそこから、update-submodule
というブランチを切って Submodule 情報を更新して、 bbbbbb
というコミットを最新にしました。
その後、git checkout master
で元ブランチに戻ると foo
ディレクトリに差分ができています。
git status
# On branch master
# Your branch is up to date with 'origin/master'.
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
# modified: foo (new commits)
これは以下のコマンドで直すことができます。 Submodule を最初期化する感じです。
git submodule update --init
cli で --recursive
オプションを使います。
git submodule update --init --recursive
今foo
という名前の Submodule があるとします。
そして、a
ブランチでは aaaaaa
が最新 、b
ブランチでは bbbbbb
が最新の Submodule があり、どちらもmaster
ブランチから切ったあと Submodule 情報が更新されている状態だとします。
このとき、恐らくa
ブランチのマージはすんなりできますが、その後b
を取り込もうとした時にコンフリクトが起こるのではないかと思います。
Submodule 周りのコンフリクトは GitHub 上のプルリクエストページ上では解決できずローカルで解決するしかないので少し複雑です。
以下の方法は僕が色々試した結果なのでベストではないかもしれません。誰か詳しい人がいたら@nju33まで教えてください。
一度消してしまいます。以下で Sumodule ディレクトリを削除すると.gitmodules
からも消えます。
git rm foo
これは追加するときと同じですね。
git submodule add [-b BRANCH_NAME] foo <GIT_URL>
もし、コミットを指定したものに変えたいなら以下でできます。
#1つしかない、または全部
git submodule foreach git reset --hard <COMMIT_HASH|TAG_NAME|branch>
このブランチだけ指定したいんだという場合は以下でできます。その Submodule のルートディレクトリを見て、「BRANCH_NAME
が入っていれば HEAD コミットを変える」ということをしています。こちらももっといい方法があれば@nju33に教えてください。
git submodule foreach sh -c 'if echo `git rev-parse --show-toplevel` | grep <BRANCH_NAME> > /dev/null; then git reset --hard <COMMIT_HASH|TAG_NAME|branch>; fi'
ちなみにここで間違えた場合は以下で戻しましょう。
git submodule update --init
Upstream Branch は、上流ブランチとも呼ばれるようです。
あるローカルブランチがあるとして、そのブランチでの操作ターゲットを省略した時に暗黙的に対象となるようなリモートブランチを設定できます。例えば今のローカルにあるものはリモートAとリモートBに定期的にpush
しているとして、リモートAには特に頻繁にpush
している時、
git rev-parse --abbrev-ref HEAD
# master ブランチにいる
git branch -u remote-a/master
# ローカルの master ブランチのデフォルトリモート先を remote-a/master に設定
# `git branch --set-upstream-to remote-a/master`のエイリアスを使ったもの
とすると次からgit push
だけでgit push remote-a master
の意味にしてくれます。リモートBの場合は変わらずgit push remote-b master
となります。
多分馴染みの食堂で「いつもの」と言っただけで決まった料理が出てくるようなものだと思います。違うのは初回から馴染みの客になれちゃうところでしょうか。
ちなみにgit push -u ...
と-u
フラグを付けると、まだ Upstream Branch が設定されていないならその時のリモートブランチを Upstream Branch に設定してくれます。上書きするには、git branch -u ...
をする必要があります。
以下を実行すると一覧で出してくれます。
git branch -vv
# * develop 5eac0f0 [origin/develop] commit-message
# master 9e51003 [origin/master: behind 48] commit-message
設定ファイルなどで良く起こるんじゃないかなと思います。ローカルでだけデーターベースの接続先情報を変更したりだとか、環境変数をローカル開発用にするとか色々。git checkout <file-name>
とかgit add <...file-name>
してgit reset --hard
すればいいだけなんだろうけど、やらなくていい方法があるならそれがいいので調べました。
これだけのコマンドでできました。
git update-index --assume-unchanged <...file-name>
<...file-name>
には管理対象外にしたいファイルのパスを羅列すればいいです。これでgit status
からしばらく消し去ることができました。
わざわざ対象外にした設定ファイルなどに変更を入れたくなった場合、以下のコマンドで再度管理対象にできます。
git update-index --no-assume-unchanged <...file-name>
またgit status
に現れるようになりました。
インデックス管理を専門にしたコマンドみたいです。例えばgit add ...
という頻繁に使うコマンドはgit update-index --add ...
のエイリアスのようです。
これは以下で取得できます。SUBMODULE_DIR_NAME
は Submodule がマウントされているディレクトリ名に置き換えます。
git submodule --quiet foreach sh -c 'if echo `git rev-parse --show-toplevel` | grep SUBMODULE_DIR_NAME > /dev
/null; then echo `git log -1 --format="%H"`; fi'
# 75eb096b9114701bee035c203310e4f13aae365d
git submodule foreach
は各 Submodule ディレクトリの中に入って何か処理を行う為のコマンドです。そのままだとEntering 'SUBMODULE_DIR_NAME'
のように「今ここにいますよ」適なログがでますが、今回みたいな値を取りたいときなどには不要ものなので、--quiet
というフラグを付けて非表示にしています。fish
シェルを使っているのですが、どうしてもif
文がシンタックスエラーになり認識してくれませんでした。なので、sh -c
で囲って?います。これだと認識してくれました。git rev-parse --show-toplevel
でその Submodule のルートディレクトリの絶対パスを取得して、grep SUBMODULE_DIR_NAME
で扱いたい Submodule のディレクトリかを調べています。扱いたい Submodule だった場合のみ、if
の中身が実行されます。echo $(git log -1 --format="%H")
をします。これは-1
は最新のコミットだけを対象にして、そのコミットのハッシュだけを表示するようにカスタマイズしたgit log
です。git submodule --quiet foreach \
sh -c '
if echo `git rev-parse --show-toplevel` | grep SUBMODULE_DIR_NAME > /dev/null
then
echo `git log -1 --format="%H"`
fi
'
Submodule の更新を都度プルリクで行うような場合、そのプルリク作成時に以下のように実行すると最新ハッシュを含んだブランチ名にできます。
git checkout -b SUBMODULE_DIR_NAME/(上の内容)
# すでにいる場合は以下でブランチ名リネームで
git branch -M SUBMODULE_DIR_NAME/(上の内容)
以下でできます。
git rev-list 00000..HEAD | xargs -I@ git log @ -1 --format="%h %B" | grep .
git rev-list <COMMIT_HASH>..HEAD
でそのコミットから先頭までのすべてのコミットハッシュが得られます。ちなみにCOMMIT_HASH
はタグとかでもいいです。xargs -I@ git log @ -1 --format="%h %B"
でそれをSHORT_COMMIT_HASH COMMIT_MESSAGE
の形に整形します。git rev-list --format
だとcommit ...
というメッセージが一緒に出されてしまうのでこの方法にしています。grep .
空行を削除するようですgit tag -a v3.0.0 -m (上のコマンド)
とかで使えるかもしれません。
例えば、最初から初めたいけど一応履歴としても残しておきたいような時に。
これには、--orphan
という聞き慣れないフラグを使います。
git branch --orphan NEW_BRANCH_NAME
これに成功すると履歴は空っぽでかつ依存のファイルがすべてgit add --all
したような状態になっていると思います。ファイルを使うならそのままでいいですが、ファイルも削除したい場合は、
git rm --rf .
rm -rf *
で空っぽにできます。
non-bare で現在選択中のリポジトリにgit push
しようとすると以下のようなエラーがでます。
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
これは以下をignore
の設定をするとこで弾かなくできます。
git config receive.denyCurrentBranch ignore
何かリモート先の自動スクリプトか何かでうまくgit push
できないことがあると、リモートのファイルの元の状態と更新後の状態とで差分(更新後の状態から、元の状態に変更したような状況)がでる時があるので、それはgit reset --hard
で更新後の状態に戻しましょう。
ちなみにこれはpost-receive
フックを使うと自動化できるので、権限があるなら設定しておくと便利です。post-receive
ファイルはこのような感じになります。
#!/bin/sh
cd <target_dir>;
git --git-dir=.git reset --hard
実行権限も与えておきます。
chmod +x .git/hooks/post-receive