1番新しく付けたタグ名を表示する

これで取得できます。

git tag --sort=-taggerdate | sed -n 1p

--sort=-taggerdateでタグを付けた日が新しい→古い順に並び替えができます。それをsedで最初の1行目だけにしているだけです。

$ git tag 1
$ git tag 2
$ git tag 2
$
$ git tag
1
2
3
$ git tag --sort=-taggerdate
3
2
1
$ git tag --sort=-taggerdate | sed -n 1p
3

GitHub API で新しい参照を作る時に422(Unprocessable Entity)エラーが出た

原因

新しい参照を作る時に出た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`する

大抵はこれで持ってくるはずです。

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

submodule を作り直す方法

# .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

少し解説

  1. git diff --name-onlyはファイル名だけを羅列するのでそれをgrepでほしいファイルだけに絞り込み
  2. git checkout <とあるブランチ> <filename>でとあるブランチ状態のそのファイルの状態にチェックアウト
  3. for...inで上2つを繰り返し実行

GitHubのプルリクページをCLIから開く

これは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`

bashエイリアス

こんな感じで登録すると便利か…も?

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ブランチで開く

新しく作ったブランチで Submodule 情報を更新後、元ブランチに戻ると submodule の差分が残ったら

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 の差分を解決する

これは以下のコマンドで直すことができます。 Submodule を最初期化する感じです。

git submodule update --init

Submodule を削除する

Submodule の削除は複雑です。

git --version
# git version 2.20.1 (Apple Git-117)

場面毎の処理

git 管理はまだ

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 で管理済かつ、Init 済の場合

# 紐付けを解除
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>

Submodule のコミットチェックアウト先を現在管理している位置に戻す

以下の手順でできます。

  1. 管理しているサブモジュールのコミットハッシュを取得
  2. サブモジュールの中でそのコミットハッシュにgit reset --hard
  3. サブモジュールを更新する
# 管理しているサブモジュールのコミットハッシュを取得
git submodule status
# +561f1760b97ad7e0743e9d300828b18842b65d83 ...

sh -c 'cd <submodule-name> && git reset --hard 561f1760b97ad7e0743e9d300828b18842b65d83'

# ブモジュールを更新
git submodule update
# Submodule path '<submodule-name>': checked out '9c949cf0bd3e326f30ac3147247e21cd0ad29517'

Submodule 同士でコンフリクリしたときの解決方法

fooという名前の Submodule があるとします。
そして、aブランチでは aaaaaa が最新 、bブランチでは bbbbbb が最新の Submodule があり、どちらもmaster ブランチから切ったあと Submodule 情報が更新されている状態だとします。

このとき、恐らくaブランチのマージはすんなりできますが、その後bを取り込もうとした時にコンフリクトが起こるのではないかと思います。
Submodule 周りのコンフリクトは GitHub 上のプルリクエストページ上では解決できずローカルで解決するしかないので少し複雑です。

以下の方法は僕が色々試した結果なのでベストではないかもしれません。誰か詳しい人がいたら@nju33まで教えてください。

Submodule 同士のコンフリクトを解消する

問題の Submodule を削除する

一度消してしまいます。以下で Sumodule ディレクトリを削除すると.gitmodulesからも消えます。

git rm foo

問題の Submodule を再度設定する

これは追加するときと同じですね。

git submodule add [-b BRANCH_NAME] foo <GIT_URL>

Submodule 側のあるコミットを HEAD に指定したい

もし、コミットを指定したものに変えたいなら以下でできます。

#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 を設定して push や pull 時の対象を省略する

Upstream Branch は、上流ブランチとも呼ばれるようです。

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となります。

多分馴染みの食堂で「いつもの」と言っただけで決まった料理が出てくるようなものだと思います。違うのは初回から馴染みの客になれちゃうところでしょうか。

push 時に一緒に設定する

ちなみにgit push -u ...-uフラグを付けると、まだ Upstream Branch が設定されていないならその時のリモートブランチを Upstream Branch に設定してくれます。上書きするには、git branch -u ...をする必要があります。

現在の各ブランチの Upstream Branch 設定状況を確認する

以下を実行すると一覧で出してくれます。

git branch -vv
# * develop       5eac0f0 [origin/develop] commit-message
#   master        9e51003 [origin/master: behind 48] commit-message

ローカルだけの gitignore

.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 管理されてるけど変更は入れちゃだめだし、どうするかももう聞かれたくない時

設定ファイルなどで良く起こるんじゃないかなと思います。ローカルでだけデーターベースの接続先情報を変更したりだとか、環境変数をローカル開発用にするとか色々。
git checkout <file-name>とかgit add <...file-name>してgit reset --hardすればいいだけなんだろうけど、やらなくていい方法があるならそれがいいので調べました。

.gitignore に入れずに管理対象外にする

これだけのコマンドでできました。

git update-index --assume-unchanged <...file-name>

<...file-name>には管理対象外にしたいファイルのパスを羅列すればいいです。これでgit statusからしばらく消し去ることができました。

一時的に管理対象外にしたファイルを再度管理対象にする

わざわざ対象外にした設定ファイルなどに変更を入れたくなった場合、以下のコマンドで再度管理対象にできます。

git update-index --no-assume-unchanged <...file-name>

またgit statusに現れるようになりました。

というか update-index ってなんだろう

インデックス管理を専門にしたコマンドみたいです。例えばgit add ...という頻繁に使うコマンドはgit update-index --add ...のエイリアスのようです。

ある Submodule の HEAD コミットハッシュをワンライナーで取得

これは以下で取得できます。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

説明

  1. git submodule foreachは各 Submodule ディレクトリの中に入って何か処理を行う為のコマンドです。そのままだとEntering 'SUBMODULE_DIR_NAME'のように「今ここにいますよ」適なログがでますが、今回みたいな値を取りたいときなどには不要ものなので、--quietというフラグを付けて非表示にしています。
  2. 僕はfishシェルを使っているのですが、どうしてもif文がシンタックスエラーになり認識してくれませんでした。なので、sh -cで囲って?います。これだと認識してくれました。
  3. git rev-parse --show-toplevelでその Submodule のルートディレクトリの絶対パスを取得して、grep SUBMODULE_DIR_NAMEで扱いたい Submodule のディレクトリかを調べています。扱いたい Submodule だった場合のみ、ifの中身が実行されます。
  4. そして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/(上の内容)

HEAD からあるコミットまでのコミットメッセージを取得

以下でできます。

git rev-list 00000..HEAD | xargs -I@ git log @ -1 --format="%h %B" | grep .

少し解説

  1. git rev-list <COMMIT_HASH>..HEADでそのコミットから先頭までのすべてのコミットハッシュが得られます。ちなみにCOMMIT_HASHはタグとかでもいいです。
  2. xargs -I@ git log @ -1 --format="%h %B"でそれをSHORT_COMMIT_HASH COMMIT_MESSAGEの形に整形します。git rev-list --formatだとcommit ...というメッセージが一緒に出されてしまうのでこの方法にしています。
  3. ただgrep .空行を削除するようです

次バージョンのタグのメッセージに使う

git tag -a v3.0.0 -m (上のコマンド)

とかで使えるかもしれません。

履歴が空っぽのブランチを切る

まったく別の内容を同じリポジトリ内で持ちたい場合現在の履歴は不要なのでまっさらな状態が欲しくなる場合があります。これには、--orphanオプションを使います。

git checkout --orphan <new branch name>

成功すると履歴は空っぽのブランチにチェックアウトします。ただ、依存のファイルがすべてgit add --allしたような状態になっているので以下ですべて削除することで、中身も空にできます。

git rm --rf .
rm -rf *

master ブランチの履歴をリセット

checkout --orphanの後以下のようにすることでmasterブランチの履歴をリセットできます。

  1. git branch -D mastermasterブランチを削除
  2. checkout --orphan masterで新たにmasterブランチを作成
  3. checkout masterで移動してコミット

non-bare なリポジトリにプッシュする

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 のコマンド

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

こなみにこれらは、一旦指定コミットまで戻った上で上(古いコミット)から下(新しいコミット)に処理されていきます。

p, pick

デフォルトではこれになっていると思いますが、これはそのままそのコミット内容を再適用するということになります。

r, reword

これを指定したコミットの適用前にエディターが開いてコミットメッセージを変更できます。

e, edit

そのコミットを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で次のコミットへ移動します。
これはそのコミットのauthor情報の書き換えがしたいような場合に便利なモードです。

s, squash

そのコミットのメッセージと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

...

f, fixup

squashと似てますが、そのコミットのpush内容だけが対象です。メッセージは単に捨てられます。
また、rebase後のコミットのメッセージ編集も立ち上がりません。

x, exec

完全にやり直したい場合に使えます。そのコミット内容を完全に捨ててしまい、やりたければまったく新しいコミットをその場で作成・適用ができます。このコマンドの実行時にはそのタイミングで待機状態になります。以下はそのときの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。。。)

d, drop

単にそのコミットを削除します。

これはたまにmasterを経由しなければいけないのに、平行で作業中のブランチから新しいブランチと移動してしまい作業中だったブランチの内容を持ってきてしまった時にブランチを作り直さなくても良くなったりします。

allow-empty は表示されない

これを調べる時に--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にもできます)

コンフリクト時に自分(相手)が持っている内容をそのまま使う

コンフリクト時に対象のファイルでは「自分の内容使う?それとも相手の内容?はたまた両方?」のように聞かれます。その時に手動でファイルを更新してもいいですが、見るまでもなく明らかな時、git checkoutで解決できる方法があります。

それには--ours--theirsオプションを使います。oursで「自分の持ってる内容使ってね」、theirsでは自分じゃなく相手になります。

foo.txtがコンフリクトを起こしてるとします。どちらかを採用する時が明らかな時は以下のようにすると良いです。

git checkout --ours foo.txt
git checkout --theirs foo.txt

コミット編集で user.name と user.email を書き換える

まずコミットメッセージの編集はgit commit --amend、またその後ろに-m <新しいコミットメッセージ>なコマンドを実行することで行なえます。

しかしコミットをfoo <foo@example.com>な時に行ったコミットをbar <bar@example.com>な時にgit commit --amendしてもfoo <foo@example.com>のままになってしまいます。

user.nameuser.emailも書き換えたいなら--authorオプションを使います。よって書き換えコマンドの正解は以下になります。(--no-editはコミットメッセージを編集しないオプション)

git commit \
  --amend \
  --no-edit \
  --author "bar <bar@example.com>"
  

複数のコミットまとめて

既にいくつかのコミットを入れてしまっている場合はgit rebase -iが使えます。後にHEAD~[書き換えたいコミット数]と続けて、全てのコミットでeditを選択します。

あとは次のようなコマンドを連続で実行するだけです。

git commit --amend --no-edit --author 'bar <bar@example.com>'
git rebase --continue

origin 以外の名前で clone する

git clone <url>と実行すると、そのリモート URL にはoriginという名前が付けられます。これにより最初は何も設定せずともgit push origin ...のようにpushできるようになり便利な機能ですが、時々originでは持ってきたくない場合があります。例えば、originとなる予定とは別の場所からまずcloneしてきたい場合などです。

git cloneには-o, --originオプションがありここでリモート URL 名を設定できます。例えば以下で持ってくると、

git clone <url> --origin foo

リモート情報は、

foo <url> (fetch)
foo <url> (push)

になります。

つまりgit clone <url>git clone <url> --origin originとしてるのと同じという訳ですね。

history を辿ってあるファイルが削除・追加されたコミットを調べる

git pullしたら何故かファイルが無くなっておりビルドが通らなくなった」ということが過去にあり、その時に調べた方法です。一部変えれば削除だけでなく追加も調べられるのでついでに。

(追記)以下のようなことをしなくても削除コミットは、

git log <relative-filepath>

だけで調べられます。この時1番最初に出てきたログが削除コミットです。
<relative-filepath>はリポジトリルートからの相対パスが入ります。

  1. 削除コミット
  2. 追加コミット

削除コミット

for h in $(git log --no-merges --format="%H" -- <filename>)
do
  if test $(git show $h --name-only --format="" --diff-filter D | grep <filename>)
  then
    echo $h
    break
  fi
done
# あれば `cfb0099a5ea07f474bbe197783d96ac5294d85dd` のようなコミットハッシュ

2つある<filename>へは削除されたファイル名の相対パスを書きます。

このスクリプトの本体は大体、

git show <commit-hash> --name-only --format="" --diff-filter D | grep <filename>

を繰り返し実行してることになります。

この1行は<commit-hash>のコミットで削除されたファイルの中から<filename>なものがあるか調べるコマンドです。show--diff-filter Dを追加するとそうした削除したファイルのみを表示させることができます。
また--name-onlyを付けてファイル名だけ、--format=""で、

commit <hash>
Author: <author>
Date:   <author date>

のようなヘッドが出力されないようにしてます。

対象のファイルが出現したタイミング(削除されたコミット)を見つけると、そのコミットハッシュを表示して処理を終わります。そして見つけたコミットはgit show <commit-hash>でじっくり眺めることができます。

追加コミット

--diff-filterはファイルの状態で絞り込むオプションです。例えば削除ならD、追加ならA、変更ならMのように指定します。

もう大体お分かりのように追加コミットを調べるには--diff-filterAに変更するだけです。

for h in $(git log --no-merges --format="%H" -- <filename>)
do
  if test $(git show $h --name-only --format="" --diff-filter A | grep <filename>)
  then
    echo $h
    break
  fi
done
# あれば `cfb0099a5ea07f474bbe197783d96ac5294d85dd` のようなコミットハッシュ

Alias

コマンドに別名を付けて新しい Git コマンドを作れます。例えばcommitccommit --amendcaと別名を付ければ、それぞれgit cgit caで実行できるようになります。

git configには範囲をある程度絞れる[scope]オプションを指定できます。これは主に以下の2つを使います。

  1. --global
  2. --local

--globalを使うとそのユーザーでの全てのリポジトリで、設定した別名が有効になり、--localだと1つのリポジトリのみでその別名が有効になります。

Alias を登録・削除

登録

登録は以下のようなコマンドを叩きます。

git config [scope] <alias-name> <command>

例えばcommitcという別名を付けたい場合は、

git config [scope] alias.c commit

のように叩きます。ちゃんと登録できたかどうかは、そのコマンドを叩いてみるか、

git config --get <alias-name>

と叩いて、登録した別名が表示されれば大丈夫です。

削除

削除する場合は--unsetオプションを使います。

git config [scope] --unset <alias-name>

cという Alias を削除したい場合は、

git config [scope] --unset alias.c

のように叩きます。

Untracked files の名前だけを一覧する

新規にファイルを作った後などにgit statusするとUntracked files:以下にその作られてたファイルが一覧されるはずです。

On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        new-file.txt

シェルなどでそこで一覧されるファイルを扱う必要があったので、調べた所以下-s, --shortオプションを付ける方法でいけました。元々git statusで出力される結果はlongフォーマットですが、shortフォーマットにすることで出力は<status> <file-name>のような形になります。

git status --short
# ?? new-file.txt

Untracked files は??というステータスと一緒に表示されます。これさえ取得できれば後はシェルのパイプを繋げてファイル名だけにします。

git status --short | grep -E "^\?\?" | cut -d' ' -f 2
# new-file.txt