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.
Gratuluję bardzo dobry wpis! Widzę, że nie tylko mnie git zaskakuje niemalże każdego dnia.