9

コミットされていない作業上の変更を無視して、インデックスの現在の状態でプロジェクトのテストを実行できるようにしたい(後でこれをpre-commitフックに追加する予定です)。ただし、マージの競合を引き起こさない方法で、インデックス以外の変更を削除してから復元する方法を理解するのに問題があります。スクリプトによって実行されているため、これが必要です。終了時にリポジトリの状態を変更しないでください。

git stash --include-untracked --keep-index2つのコマンド間で変更が行われていなくても、多くの場合、マージの競合が発生しgit stash popます。

例えば:

mkdir project; cd project; git init .;

# setup the initial project with file.rb and test.rb
cat > file.rb <<EOF
def func()
  return 42
end
EOF

cat > test.rb <<EOF
#!/usr/bin/env ruby
load './file.rb'
if (func() == 42)
  puts "Tests passed"
  exit 0
else
  puts "Tests failed"
  exit 1
end
EOF

chmod +x test.rb
git add .
git commit -m "Initial commit"

# now change file.rb and add the change
cat > file.rb <<EOF
def func()
  return 10 + 32
end
EOF
git add file.rb

# now make a breaking change to func, and don't add the change
cat > file.rb <<EOF
def func()
  return 20 + 32 # not 42 anymore...
end 
EOF

ここから、インデックスの現在の状態に対してテストを実行し、コミットされていない変更を復元します。重大な変更がインデックスに追加されなかったため、期待される結果はテストに合格することです。

次のコマンドは機能しません。

git commit --include-untracked --keep-index
./test.rb
git stash pop

問題はで発生しますgit stash pop-マージの競合が発生します。

私が考えることができる他の唯一の解決策は、一時的なコミットを行い、残りの変更をスタッシュし、コミットをロールバックしてgit reset --soft HEAD~、スタッシュをポップすることでした。ただし、これはどちらも面倒であり、コミット前のフックで実行するのがどれほど安全かはわかりません。

この問題に対するより良い解決策はありますか?

4

3 に答える 3

9

あなたのように、私は走ります

git stash --keep-index --include-untracked

その後、テストなどを実行できます。

次の部分は注意が必要です。これらは私が試したいくつかのことです:

  • git stash pop競合で失敗する可能性がありますが、これは許容できません。
  • git stash pop --index競合で失敗する可能性がありますが、これは許容できません。
  • git checkout stash -- .追跡されたすべての変更を適用し(良好)、ステージングも行い(許容できない)、追跡されていないファイルをスタッシュから復元しません(許容できない)。隠し場所は残ります(罰金-私はできますgit stash drop)。
  • git merge --squash --strategy-option=theirs stash競合で失敗する可能性がありますが、これは許容できません。競合しない場合でも、追跡されていないファイルをスタッシュから復元しません(許容できません)。
  • git stash && git stash pop stash@{1} && git stash pop(チェンジセットを逆の順序で適用しようとすると)競合が発生して失敗する可能性があり、これは許容できません。

しかし、私は私たちが望むことを行うコマンドのセットを見つけました:

# Stash what we actually want to commit
git stash
# Unstash the original dirty tree including any untracked files
git stash pop stash@{1}
# Replace the current index with that from the stash which contains only what we want to commit
git read-tree stash
# Drop the temporary stash of what we want to commit (we have it all in working tree now)
git stash drop

出力を減らし、1行にまとめるには:

git stash --quiet && git stash pop --quiet stash@{1} && git read-tree stash && git stash drop --quiet

私の知る限り、これで復元されないのは、インデックスに追加されてから作業ツリーから削除されたファイル(最終的には追加されて存在する)と、インデックスで名前が変更されたファイルとその後、作業ツリーから削除されます(同じ結果)。このため、最初の隠し場所の前のような行でこれら2つのケースに一致するファイルを探し、git status -z | egrep -z '^[AR]D' | cut -z -c 4- | tr '\0' '\n'復元後にループして削除する必要があります。

git stash --keep-index --include-untracked明らかに、作業ツリーに追跡されていないファイルまたはステージングされていない変更がある場合にのみ、イニシャルを実行する必要があります。git status --porcelain | egrep --silent '^(\?\?|.[DM])'これを確認するには、スクリプトでテストを使用できます。

これは既存の回答よりも優れていると思います-中間変数(ツリーが汚れているかどうか、およびスタッシュの復元後に削除する必要があるファイルのレコードを除く)を必要とせず、コマンドが少なく、安全のためにガベージコレクションをオフにする必要はありません。中間の隠し場所がありますが、私はこれがまさに彼らが求めている種類のものであると主張します。

これが私の現在のpre-commitフックで、上記のすべてを実行します。

#!/bin/sh

# Do we need to tidy up the working tree before tests?
# A --quiet option here doesn't actually suppress the output, hence redirection.
git commit --dry-run >/dev/null
ret=$?
if [ $ret -ne 0 ]; then
    # Nothing to commit, perhaps. Bail with success.
    exit 0
elif git status --porcelain | egrep --silent '^(\?\?|.[DM])'; then
    # There are unstaged changes or untracked files
    dirty=true

    # Remember files which were added or renamed and then deleted, since the
    # stash and read-tree won't restore these
    #
    # We're using -z here to get around the difficulty of parsing
    # - renames (-> appears in the string)
    # - files with spaces or doublequotes (which are doublequoted, but not when
    #   untracked for unknown reasons)
    # We're not trying to store the string with NULs in it in a variable,
    # because you can't do that in a shell script.
    todelete="$(git status -z | egrep -z '^[AR]D' | cut -z -c 4- | tr '\0' '\n')"
else
    dirty=false
fi

if $dirty; then
    # Tidy up the working tree
    git stash --quiet --keep-index --include-untracked
    ret=$?

    # Abort if this failed
    if [ $ret -ne 0 ]; then
        exit $ret
    fi
fi

# Run tests, remember outcome
make precommit
ret=$?

if $dirty; then
    # Restore the working tree and index
    git stash --quiet && git stash pop --quiet stash@{1} && git read-tree stash && git stash drop --quiet
    restore_ret=$?

    # Delete any files which had unstaged deletions
    if [ -n "$todelete" ]; then
        echo "$todelete" | while read file; do
            rm "$file"
        done

        # Abort if this failed
        if [ $restore_ret -ne 0 ]; then
            exit $restore_ret
        fi
    fi
fi

# Exit with the exit status of the tests
exit $ret

どんな改善も歓迎します。

于 2014-11-01T00:56:34.577 に答える
4
$ git config gc.auto 0   # safety play
$ INDEX=`git write-tree`
$ git add -f .
$ WORKTREE=`git write-tree`
$ git read-tree $INDEX
$ git checkout-index -af
$ git clean -dfx
$ # your tests here
$ git read-tree $WORKTREE
$ git checkout-index -af
$ git clean -dfx
$ git read-tree $INDEX
$ git config --unset gc.auto
$ # you're back.

-xのgit cleanマンページは、この解決策をかなり楕円形に示唆しています

于 2012-12-15T17:55:03.123 に答える
0

これが私がこれまでに考え出した最良の解決策です。gitフックpre-commitとで動作しcommit-msgます。次の場合にテストしました。

  • 段階的な変更のみ
  • ステージングされていない変更のみ
  • 段階的変更と非段階的変更の両方
  • 変更なし

3つのケースすべてで、正しく機能します。大きな欠点は、それがかなりハッキーであり、一時的なコミットとスタッシュを作成および削除することです。

#!/bin/bash

git commit -m "FOR_COMMIT_MSG_HOOK" -n
commit_status=$?
# git stash save always returns 0, even if it
# failed creating a stash. So compare the new and old git stash list
# output to see if a stash was created
current_stash_count="$(git stash list)"
git stash save -q -u "FOR_COMMIT_MSG_HOOK"
new_stash_count="$(git stash list)"

echo "##### Running tests #####"
# put testing code here


if [[ $commit_status -eq 0 ]]; then
    git reset --soft 'HEAD~'
fi

if [[ "$current_stash_count" != "$new_stash_count" ]]; then
  git stash pop -q
fi

exit $result
于 2012-12-16T00:48:27.570 に答える