Upojne chwile z git filter-branch

1

Git nie przestaje zaskakiwać. Zawsze gdy pomyślę "fajnie by było, gdyby się dało [cokolwiek]" okazuje się, że w Gicie.. się DA!

Ostatnio eksperymentowałem z jakąś biblioteką i nie chciałem tymi zabawami śmiecić w firmowym TFSie. Utworzyłem więc oczywiście lokalne repozytorium Gita i – jazda! Po kilku dniach okazało się jednak, że wykluło się z tego coś pożytecznego i mimo wszystko fajnie by było efekty pracy w tymże TFSie mieć, w jednym z firmowych projektów. Można było pójść na skróty i po prostu skopiować finalną postać mojego kodu, ale ja preferowałem wrzucić do TFSa całą historię, commit po commicie. W niniejszym poście poruszę dwie sprawy: przygotowanie mojego lokalnego repo do tej czynności oraz sam proces łączenia repozytoriów.

Note: kiedyś pisałem już o modyfikacji historii w Gicie; ten post idzie jeszcze dalej.

Git filter-branch – oh jea!

Moje lokalne repozytorium miało strukturę przygotowaną pod ten konkretny projekt, a więc w roocie siedział sobie katalog /lib, obok katalog /src z kodem. Repozytorium w TFS przechowuje jednak więcej informacji i docelowo wszystkie moje commity, zamiast w katalogu /, powinny lądować w katalogu, dajmy na to, /Dev/Main/MAPlayground. Natknąłem się na wielce imponującą ze wszech miar komendę filter-branch. Polecam zapoznanie się z dokumentacją, chociaż, jak to w Gicie, i bez tego ciężko jest cokolwiek poważnie zepsuć. Warto jednak popatrzeć co jeszcze, oprócz tego co tutaj przedstawiam, oferuje ta komenda. A oferuje BARDZO wiele.

Najpierw w swoim repozytorium stworzyłem osobną gałąź do takich eksperymentów z Gitem, właśnie po to żeby nic nie napsocić i w razie emergency móc spokojnie wrócić do mastera i zacząć od nowa:

git checkout -b filtered

Zanim jednak zacznę bawić się mieszaniem w historii na poziomie katalogów, czyli przepisywaniem wszystkich commitów pod względem położenia plików na dysku, chciałbym do każdej commit message dodać prefix. Komentarze w tym repo dotyczą oczywiście wyłącznie tego co w nim robiłem i chciałbym później w TFS móc w prosty sposób zgrupować commity, które zamierzam tam właśnie pchnąć. Prefix "[ma-playground]" przed każdą commit msg wydał mi się dobrym pomysłem (oczywiście tak naprawdę brzmiał trochę inaczej;) ). Po kilku chwilach zabawy doszedłem do takiej komendy:

git filter-branch –msg-filter \
‘read m;
echo "[ma-playground] $m" ‘

i już, szybkie zerknięcie w logi wywołuje uśmiech na mordzie: każdy mój commit jest teraz poprzedzony wybranym przeze mnie prefixem. Słitaśnie.

Pora na zmierzenie się z katalogami. Każdy commit powinien się teraz znaleźć we wspomnianym wyżej folderze. Kolejne kilka (no dobra… trochę więcej:) ) minut i oto rozwiązanie:

git filter-branch  –tree-filter \
‘mkdir Dev && mkdir Dev/Main && mkdir Dev/Main/MAPlayground
ls -A | grep -v Dev | while read filename
do
mv $filename Dev/Main/MAPlayground
done’

Windowsowym UI-klikaczom może wydać się to trochę zamotane, ale polecam zagłębienie się w ów miniskrypt i przeanalizowanie wykorzystanych w nim komend. Niejednokrotnie potrafią uprościć życie, zdecydowanie warto je znać.

Na stronie dokumentacji komendy znajduje się alternatywny sposób poradzenia sobie z tym zadaniem (operujący bezpośrednio na indexie), ale jest mocniej skomplikowany niż moja propozycja (chociaż moja propozycja jest skrojona dokładnie na miarę właśnie moich konkretnych wymagań i pewnie znajdą się przypadki kiedy nie zadziała).

Nowy git remote i rebase

Z tak przygotowanym repo czas na operacje na moim docelowym repozytorium, podłączonym do TFSa oczywiście poprzez git-tfs. Oba siedzą sobie bezpiecznie w katalogu c:\dev. Najsampierw wchodzę bashem do katalogu zawierającego repo TFSowe i dodaję link do repozytorium, które dopiero co tak pięknie obrobiłem:

git remote add ma ../MaPlayground

Teraz ściągam jego zawartość…

git fetch ma

… i ponownie tworzę gałąź "roboczą", tym razem bazując na branchu przygotowanym do operacji łączenia repo:

git checkout -b br-ma ma/filtered

Jedyne co chcę zrobić to "nałożyć" moje commity z podlinkowanego repo do repozytorium na wierzch aktualnej historii (na mastera). Jak wiemy ze wspomnianych wcześniej postów, posłużyć nam może polecenie rebase:

git rebase master

A potem już tylko:

git tfs rcheckin
git checkout master
git tfs pull
git remote rm ma
git branch -d br-ma

… i jesteśmy w domu.

Piękne, czyż nie? Ależ TAK, jak najbardziej! Zachęcam do dalszych zabaw z tym poleceniem, możliwości wydają się nieograniczone.

Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

1 Comment