web-dev-qa-db-ja.com

index-filter&coを使用してgitリポジトリからコミット履歴を含む1つのファイルを抽出する方法

私の状況は、gitリポジトリをSVNからHG、GITに変換していて、ソースファイルを1つだけ抽出したかったというものでした。また、ファイル名にaÌ(エンコーディングの不一致により破損したUnicodeä)やスペースなどの奇妙な文字が含まれていました。

それは特に簡単ではないようです。これを達成するために以前のすべてを使用する必要があったので、git [index-filter | subdirectory-filter | filter-tree]に関する多くの同様の質問にもかかわらず、私が自分の質問に答える理由です!

したがって、問題は、「リポジトリから1つのファイルを抽出して、それを新しいリポジトリのルートに配置するにはどうすればよいですか?」です。

43
peterhil

最初に簡単な注意点として、 gitリポジトリ内のファイルのセットを独自のリポジトリに分割し、関連する履歴を保持する に関するコメントのようなスペルでも

SPELL='git ls-tree -r --name-only --full-tree "$GIT_COMMIT" | grep -v "trie.LISP" | tr "\n" "\0" | xargs -0 git rm --cached -r --ignore-unmatch'
git filter-branch --Prune-empty --index-filter "$SPELL" -- --all

imaging/DrinkkejaI<0300>$'\302\210'.txt_74x2032.gifのような名前のファイルでは役に立ちません。 aI<0300>$'\302\210'の部分は、かつては1文字でした:ä

したがって、単一のファイルを抽出するには、フィルターブランチに加えて、次のことも行う必要がありました。

git filter-branch -f --subdirectory-filter LISP/source/model HEAD

または、-tree-filter:を使用することもできます(ファイルは以前に別のディレクトリにあったため、テストが必要です。以下を参照してください: すべてのコミットに対してGitリポジトリ内のディレクトリを移動するにはどうすればよいですか? ?

MV_FILTER='test -f source/model/trie.LISP && mv ./source/model/trie.LISP . || echo "Nothing to do."'
git filter-branch --tree-filter $MV_FILTER HEAD --all

ファイルに付けられているすべての名前を表示するには、次を使用します。

git log --pretty=oneline --follow --name-only git-path/to/file | grep -v ' ' | sort -u

http://whileimautomaton.net/2010/04/03012432 で説明されているように

その後の手順も実行してください。

$ git reset --hard
$ git gc --aggressive
$ git Prune
$ git remote rm Origin # Otherwise changes will be pushed to where the repo was cloned from
13
peterhil

同じことを実現する、より高速で理解しやすいフィルター:

git filter-branch --index-filter '
                        git read-tree --empty
                        git reset $GIT_COMMIT -- $your $files $here
                ' \
        -- --all -- $your $files $here
47
jthill

これを、目的のファイルを新しいディレクトリに移動する追加の手順と組み合わせると、作業がはるかに簡単になることに注意してください。

これは非常に一般的な使用例である可能性があります(たとえば、目的の単一ファイルをルートディレクトリに移動する)。
私は(git 1.9を使用して)次のようにしました(最初にファイルを移動し、次に古いツリーを削除します):

git filter-branch -f --tree-filter 'mkdir -p new_path && git mv -k -f old_path/to/file new_path/'
git filter-branch -f --Prune-empty --index-filter 'git rm -r --cached --ignore-unmatch old_path'

目的のファイルにワイルドカードを簡単に使用することもできます(grep -vをいじることはありません)。

これ(「mv」と「rm」)も1つのフィルターブランチで実行できると思いますが、うまくいきませんでした。

私は変なキャラクターでそれを試しませんでしたが、とにかくこれが役立つことを願っています。物事を簡単にすることは、私にとって常に良い考えのようです。

ヒント:
これは、大規模なリポジトリでの時間のかかるアクションです。したがって、いくつかのアクション(ファイルの束を取得してから「new_path/subdirs」に再配置するなど)を実行する場合は、できるだけ早く「rm」部分を実行して、より小さく高速なツリーを取得することをお勧めします。

11
Roman

以下は履歴を書き換え、指定したファイルのリストに触れるコミットのみを保持します。元の履歴が失われないように、リポジトリのクローンでこれを実行することをお勧めします。

FILES='path/to/file1 other-path/to/file2 file3'
git filter-branch --Prune-empty --index-filter "
                        git read-tree --empty
                        git reset \$GIT_COMMIT -- $FILES
                " \
        -- --all -- $FILES

次に、ユースケースに応じて通常のmergeまたはrebaseコマンドを使用して、その新しいブランチをターゲットリポジトリにマージできます。

2
PowerKiKi