• ..

Ansible

    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 同士でコンフリクリしたときの解決方法

    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 *

    で空っぽにできます。

    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で次のコミットへ移動します。

    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にもできます)