web-dev-qa-db-ja.com

作業ツリーを変更せずにスタッシュを保存するGitコマンド?

作業ツリーを変更せずに隠し場所を保存するgitコマンドを、gitのリセットやインデックスを台無しにするために行う可能性のあることから安全な軽量バックアップとして使用したいと思っていました。基本的に「gitstashsave && git stash apply」と機能的に同等ですが、作業コピーに触れない点が異なります。これにより、特定のテキストエディタ/ IDEが不機嫌になる可能性があるためです。

このようなものが私が望むものに近づいていますが、完全ではありません:

git update-ref refs/stash `git stash create "Stash message"`

これは機能的には機能しますが、実際のstashコミットにメッセージが含まれていても、「gitstashlist」にstashメッセージが表示されないという問題があります。スタッシュがどれだけ大きくなるかを考えると、スタッシュメッセージは非常に重要です。

43
Eliot

Charlesのヒントのおかげで、bashスクリプトを作成して、自分がやりたいことを正確に実行しました(これをエイリアスとして実装する際に問題が発生していました)。 git stash saveと同じように、オプションのstashメッセージを受け取ります。何も指定されていない場合は、gitstashによって生成されたデフォルトのメッセージが使用されます。

#!/bin/sh
#
# git-stash-snap
# Save snapshot of working tree into the stash without modifying working tree.
# First argument (optional) is the stash message.
if [ -n "$1" ]; then
        git update-ref -m "$1" refs/stash "$(git stash create \"$1\")"
else
        HASH=`git stash create`
        MESSAGE=`git log --no-walk --pretty="tformat:%-s" "$HASH"`
        git update-ref -m "$MESSAGE" refs/stash "$HASH"
fi

編集:以下のコメントで指摘されているように、このスクリプトをパスのどこかにgit-stash-snapとして保存すると、git stash-snapと入力してスクリプトを呼び出すことができます。

ここでの良い点は、このメソッドで作成されたスタッシュをドロップしても、ダングリングコミットのgit log [commit-hash]を使用してスタッシュメッセージを表示できることです。

編集:git 2.6.0以降、--create-reflogupdate-refに追加すると、git stash listが以前に使用されていなくても、git stashにこれが表示されます。

編集:Gitはstash Pushと呼ばれる新しいstashサブコマンドを導入したので、このスクリプトにgit-stash-Pushからgit-stash-snapという名前を付けるための推奨事項を更新しました。

32
Eliot

update-refはメッセージを受け取らないため、stash createではなくstash createにメッセージを渡す必要があります(refを更新しないため、入力するreflogエントリがありません)。

git update-ref -m "Stash message" refs/stash "$(git stash create)"
13
CB Bailey

git stash store "$(git stash create)"

作業ディレクトリとインデックスに実際に触れてクリアすることなく、git stashで取得するものと同様のstashエントリを作成します。

Stashリストを確認するか、すべてのコミットグラフ(stashを含む)を見ると、通常のgit stashの呼び出しで得られる結果と同様の結果であることがわかります。 stashリストのメッセージだけが異なります(通常は "stash @ {0}:WIP on master:14e009e init commit"、ここではll get "stash @ {0}:" git stash store ""を介して作成)

$ git status --short
M file.txt
A  file2.txt

$ git stash list

$ git stash store "$(git stash create)"

$ git stash list
stash@{0}: Created via "git stash store".

$ git stash show 'stash@{0}'
 file.txt  | 2 +-
 file2.txt | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

$ git log --oneline --graph --all
*   85f937b (refs/stash) WIP on master: 14e009e init commit
|\
| * 26295a3 index on master: 14e009e init commit
|/
* 14e009e (HEAD -> master) init commit

$ git status
M file.txt
A  file2.txt

もう少し説明:

Git stashエントリは、いくつかの定義された構造を持つ通常のコミットを使用して表されます。基本的には、2つの親(または--include-untrackedオプションを使用する場合は3つ)を持つ通常のコミットオブジェクトです(詳細 12 )。

git stash createは、stashエントリを表すこのコミットを作成し、コミットオブジェクト(2つまたは3つの親を持つオブジェクト)の オブジェクト名 (SHA-1)を返します。 ダングリングコミット です(git fsckの後にgit stash createを呼び出すことで確認できます)。 refs/stashがこのぶら下がっているコミットを指すようにする必要があり、git stash storeによって(または他の回答のようにgit update-refによって、git stash storegit update-ref その作業を行うため)。

git stash Pushの実際のソースコードを見て、基本的に git stash createおよびgit stash store を呼び出してから、 一部のロジック ファイルをクリーンアップします(どちらはgit stash Pushで使用したオプションによって異なります)。

12

エリオットのソリューションに触発されて、私は彼のスクリプトを少し拡張しました。

#!/bin/sh
#
# git-stash-Push
# Push working tree onto the stash without modifying working tree.
# First argument (optional) is the stash message.
#
# If the working dir is clean, no stash will be generated/saved.
#
# Options:
#   -c "changes" mode, do not stash if there are no changes since the
#      last stash.
if [ "$1" == "-c" ]; then
        CHECK_CHANGES=1
        shift
fi


if [ -n "$1" ]; then
        MESSAGE=$1
        HASH=$( git stash create "$MESSAGE" )
else
        MESSAGE=`git log --no-walk --pretty="tformat:%-s" "HEAD"`
        MESSAGE="Based on: $MESSAGE"
        HASH=$( git stash create )
fi

if [ "$CHECK_CHANGES" ]; then
        # "check for changes" mode: only stash if there are changes
        # since the last stash

        # check if nothing has changed since last stash
        CHANGES=$( git diff stash@{0} )
        if [ -z "$CHANGES" ] ; then
                echo "Nothing changed since last stash."
                exit 0
        fi
fi

if [ -n "$HASH" ]; then
        git update-ref -m "$MESSAGE" refs/stash "$HASH"
        echo "Working directory stashed."
else
        echo "Working tree clean, nothing to do."
fi

Eliotのスクリプトに次の変更を実装しました。

  1. 作業ディレクトリがクリーンな場合、スクリプトは正常に終了します
  2. スイッチ-cが使用されている場合、最後のスタッシュと比較して変更がなければ、スクリプトは終了します。これは、このスクリプトを「タイムマシン」として使用し、10分ごとに自動スタッシュを作成する場合に役立ちます。何も変更されていない場合、新しいスタッシュは作成されません。このスイッチがないと、同じn個の連続したスタッシュが発生する可能性があります。

スイッチ-cが正しく機能するためには、少なくとも1つのスタッシュが存在する必要があります。存在しない場合、スクリプトはgit diff stash@{0}でエラーをスローし、何もしません。

このスクリプトを「タイムマシン」として使用し、次のbashループを使用して10分ごとにスナップショットを作成します。

 while true ; do date ; git stash-Push ; sleep 600 ; done
4
andimeier