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
Submodule の削除は複雑です。
git --version
# git version 2.20.1 (Apple Git-117)
git submodule add
直後であれば、インデックスに登録だけされている状態だと思うのでまずこれを戻します。
git reset
その後 Submodule としたディレクトリを削除し、.gitmodules
からその Submodule 設定のセクションを削除します。
rm -rf <submodule-path>
git config --local --remove-section submodule.<submodule-path>
git config -f .gitmodules --remove-section submodule.<submodule-path>
すべての Submodule がいらないのであれば、.gitmodules
ファイル自体を削除でも大丈夫です。
# 紐付けを解除
git submodule deinit <submodule-path>
こちらの場合は上記のコマンドが.gitmodules
や.git/config
の設定を自動で消してくれるため、git config
での編集は必要ありません。
またその Submodule が必要になった場合に以下のようなエラー(警告)が出てしまうかもしれないので、Submodule のクローン済ディレクトリも削除します。それは.git/modules/<submodule-path>
に置かれています。
#
# 次回以降、そのままでは以下が出てしまうので
#
# A git directory for '<submodule-path>' is found locally with remote(s):
# origin git@github.com:user/repo.git
# If you want to reuse this local git directory instead of cloning again from
# git@github.com:user/repo.git
# use the '--force' option. If the local git directory is not the correct repo
# or you are unsure what this means choose another name with the '--name' option.
# 以下も削除しておく
rm -rf .git/modules/<submodule-path>
以下の手順でできます。
git reset --hard
# 管理しているサブモジュールのコミットハッシュを取得
git submodule status
# +561f1760b97ad7e0743e9d300828b18842b65d83 ...
sh -c 'cd <submodule-name> && git reset --hard 561f1760b97ad7e0743e9d300828b18842b65d83'
# ブモジュールを更新
git submodule update
# Submodule path '<submodule-name>': checked out '9c949cf0bd3e326f30ac3147247e21cd0ad29517'
今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/info/exclude
を編集します。このファイルは( Mac の場合?)最初こうなっています。
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store
書き方はルートに置かれている.gitignore
とまったく同じように書くことができます。例えばルートのdist
ディレクトリを除外したい場合は、
/dist
と追記するだけです。
設定ファイルなどで良く起こるんじゃないかなと思います。ローカルでだけデーターベースの接続先情報を変更したりだとか、環境変数をローカル開発用にするとか色々。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 checkout --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
git rebase -i
するとこのようなメッセージが表示されると思います。これらの動作についてです。
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command(the rest of the line) using shell
# d, drop = remove commit
こなみにこれらは、一旦指定コミットまで戻った上で上(古いコミット)から下(新しいコミット)に処理されていきます。
デフォルトではこれになっていると思いますが、これはそのままそのコミット内容を再適用するということになります。
これを指定したコミットの適用前にエディターが開いてコミットメッセージを変更できます。
そのコミットをgit commit --amend
したあとの状態で待機させます。git status
を実行すると以下のようになります。
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
何かやりたいなら再度git commit --amend
などで取り入れ、git rebase --continue
で次のコミットへ移動します。
そのコミットのメッセージとpush
内容をを一つ前のコミットに含ませ、この単体のコミットはなかった事にします。
例えば以下のような履歴だとします。
git diff HEAD~1 --name-only
# a.js
git log --oneline | sed -n 1p
# 977fa06fc add b.js
git diff HEAD~2 --name-only
# b.js
git log --oneline | sed -n 2p
# 90490591b add a.js
そしてgit rebase -i HEAD~2
を実行して以下のように編集してから閉じます。
pick 977fa06fc add a.js
s 90490591b add b.js
すると以下のようなまとめるコミットのメッセージ編集が立ち上がるので、適当に閉じます。
# This is a combination of 2 commits.
# This is the 1st commit message:
add a.js
# This is the commit message #2:
add b.js
...
squash
と似てますが、そのコミットのpush
内容だけが対象です。メッセージは単に捨てられます。
また、rebase
後のコミットのメッセージ編集も立ち上がりません。
完全にやり直したい場合に使えます。そのコミット内容を完全に捨ててしまい、やりたければまったく新しいコミットをその場で作成・適用ができます。このコマンドの実行時にはそのタイミングで待機状態になります。以下はそのときのgit status
の例です。
git status
# interactive rebase in progress; onto 02063c9
# Last commands done (3 commands done):
# pick 18819b7 foo
# exec bcac294 bar
# (see more in file .git/rebase-merge/done)
# Next command to do (1 remaining command):
# pick 403a6aa baz
# You are currently editing a commit while rebasing branch 'master' on '02063c9'.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
nothing to commit, working tree clean
「いいタイミングでgit rebase --continue
してください」とあります。何もせずにそれを実行すると次のセクションのd, drop
を適用した時と同じになります。「元コミットに無を適用した」ということで何もなかったことにしたような感じです。
このタイミングのソースの状態は、このx, exec
を適用する前の状態です。例えば、ある時点からあるバグが起こるようになってしまいその検証の為に、「怪しいある時点に戻って、その時の状態でチェックしていく」ような時に使えると思います。(そしてgit rebase --abort
。。。)
単にそのコミットを削除します。
これはたまにmaster
を経由しなければいけないのに、平行で作業中のブランチから新しいブランチと移動してしまい作業中だったブランチの内容を持ってきてしまった時にブランチを作り直さなくても良くなったりします。
これを調べる時に--allow-empty
コミットを作って確認しようと思ったのですが、一覧に表示されませんでした。駄目みたいですね。
checkout
コマンドを使います。以下は使用例です。
git checkout 4d1c4e4~1 -- <file_path>
4d1c4e4
時の変更にこのファイルの変更が含まれています。含まれているのでここに戻っても変更直後に戻るだけになってしまうので~1
により、「4d1c4e4
の1つ前」を指定しています。
変更を無かったことにするコマンドがgit checkout [HEAD] -- <file_path>
なので、考えてみれば同じ動作ですね。
ちなみに、複雑な変更であれば、
git checkout --patch 4d1c4e4~1 -- <file_path>
とすれば、塊ごとに戻すかどうか決めることが出来ます。(--patch
は-p
にもできます)