git filter-repo – co to jest + praktyczny przykład
Jeśli próbowałeś kiedyś zrobić naraz większe zmiany w historii swojego projektu np. usunąć jakieś pliki z commitów lub usunąć hasła z kodu, to pewnie natknąłeś się na narzędzie git filter-branch
. Do czego jest git filter-repo
? Do tego samego 🙂
Jak jest do tego samego, to po co o tym piszę…. ? Ponieważ teraz ludzie tworzący gita zalecają używanie filter-repo, dodatkowo jest łatwiejsze w użyciu i co ważniejsze – wydajniejsze.
Git filter-repo
Pojawiło się w wersji Gita 2.24 (3 listopad 2019 – obecnie jest wersja 2.25.1). Do działania wymaga pythona w wersji 3.5 lub nowszej. To w sumie logiczne, bo narzędzie jest własnie napisane w pythonie 🙂
To narzędzie to po prostu skrypt napisany w Pythonie. Jak go zainstalować znajdziesz tutaj: https://github.com/newren/git-filter-repo/blob/master/INSTALL.md.
UWAGA!!!
To polecenie przepisuje historię, czyli wszystkie zmienione commity będą miały inne SHA-1.
Co to potrafi:
- Usuwanie dużych plików i dużych katalogów.
- Usuwanie niechcianych plików według ścieżki.
- Wydobywanie poszukiwanych ścieżek i ich historii (usuwanie wszystkiego innego).
- Restrukturyzacja układu plików (np. przeniesienie wszystkich plików do podkatalogu w ramach przygotowań do połączenia z innym repozytorium, uczynienie z podkatalogu nowego katalogu najwyższego poziomu lub scalenie dwóch katalogów z niezależnymi nazwami plików w jednym katalogu).
- Zmiana nazw tagów (również często w ramach przygotowań do połączenia z innym repozytorium).
- Zastępowanie lub usuwanie poufnego tekstu, takiego jak hasła.
- Modyfikacje nazw użytkowników lub e-maili.
- Modyfikacje commit messages (wolę niektóre zwroty pisać w oryginale niż np. komunikaty zatwierdzania 🙂 ).
I wiele więcej… Do czego tego można jeszcze użyć? Mi przyszło do głowy takie coś.
Problem
W pracy jako systemu do tasków korzystamy z Azure DevOps. Tam każdy task ma swój numer, który jest liczbą. Pierwszy task był oznaczony nr 1 itd., teraz już chyba dochodzimy do numeru 6000.
Robiąc commit staram się łączyć go z taskiem. Najwygodniejszy sposób na takie połączenie to na końcu commit message wpisać nr taska poprzedony znakiem # czyli:
Poprawka bug-a #1
Po wrzuceniu kod na serwer ten commit będzie połączony z taskiem. Będzie to widać w portalu:
I wszystko spoko, ale czasami nie skończymy zadania i musimy na chwilę przesiąść się na naprawę czegoś innego, za pewne bardziej pilnego 🙂 Wtedy pushujemy kod na serwer (jako backup) i wracamy do tego później.
To później jest wtedy jak już na gałęzi develop (lub innej do której scalamy nasz kod, przeważnie master) są nowe rzeczy i przed przystąpieniem pracy robimy git rebase develop
. Wtedy nasz commit ma inny identyfikator. Po dokończeniu pracy i ponownym wrzuceniu na serwer, task już ma przypisane 2 commity:
W większości przypadków kod w tych commitach będzie identyczny, ale będą inne identyfikatory. W tym temacie są również zgłoszenia (o tutaj -> https://developercommunity.visualstudio.com/idea/764354/auto-link-commit-to-work-item-for-specificmaster-b.html) do Microsoftu, aby może łączyć linki tylko jeśli commit jest na głównym branchu (master/develop). Skoro nic się z tym nie da zrobić, to trzeba coś wymyślić samemu.
I tutaj z pomocą przychodzi właśnie git filter-repo.
Jak ma to nam pomóc?
Wymyśliłem to tak:
- Podczas tworzenia commita daję na końcu nr commita bez symbolu # np.
Poprawka bug-a 546
- 546 to numer naszego taska w Azure DevOps.
- W tym momencie jak wrzucę kod na serwer, to task nie zostanie połączony z commitem.
- Przed scalaniem pracy z główną gałęzią uruchamiam git filter-repo, które w moich commitach doda symbol # przed nr commita.
- Wiadomość commita nie będzie wyglądała tak:
Poprawka bug-a 546
, tylko tak:Poprawka bug-a #546
Skrypt
Napisałem do tego skrypt, żeby wykonywać to szybko i żebym nie musiał za każdym razem tego pisać 🙂 . Kod w Pythonie wykonujący tą robotę wygląda tak:
git filter-repo -f --refs master..$currentBranchName --message-callback '
lastWord = message.split()[-1]
def RepresentsInt(s):
try:
int(s)
return True
except ValueError:
return False
if RepresentsInt(lastWord):
return message.replace(lastWord, b"#" + lastWord)
else:
return message'
O co w nim chodzi… Po kolei:
git filter-repo
– wywołanie polecenia-f
– force--refs develop..$currentBranchName
– jakie commity na wziąć pod uwagę do edycji. To polecenie weźmie commity znajdujące się na naszym feature branchu, a nie ma ich na branchu develop.
UWAGA! Jeśli scalasz kod do innego brancha niż develop, zmień to w skrypcie.--message-callback + kod w Pythonie
– określenie, której opcjigit filter-repo
użyć + kod który ma być wykonany dla każdego commita. Po prostu sprawdzam czy ostatnie słowo w commit message jest liczbą. Jeśli tak to doklejam do niego # na początku. Reszta commit message pozostaje bez zmian. Jeśli commit nie posiada na końcu numeru taska, to nic się nie dzieje. Znaczy.. nie się nie dzieje z commit message, ale i tam polecenie “przemieli” tego commita i jego sha-1 będzie inne.
Cały skrypt jest dostępny na moim Githubie:
Nie znam pythona (na co dzień proramuje w C#), to co widać wyżej to efekt googlowania. Resztę skryptu postanowiłem napisać w bashu, bo już go poznałem wcześniej podczas pisania innych skryptów.
Jak taki skrypt “zainstalować” u siebie na komputerze opisał w tym wpisie: https://poznajgita.pl/jak-napisac-wlasne-polecenie-w-gicie/#podstawy
Podsumowanie
W ten oto prosty sposób – przy użyciu prostego skryptu w Pythonie – rozwiązaliśmy problem zduplikowanych połączeń tasków do commitów z kodem 🙂
git filter-repo jest narzędziem, którego można używać do wielu rzeczy. Ja pokazałem tylko jedną z nich, która może się przydać (prawie) codziennie.
Źródła:
https://git-scm.com/docs/git-filter-branch
https://github.com/newren/git-filter-repo
https://developercommunity.visualstudio.com/idea/764354/auto-link-commit-to-work-item-for-specificmaster-b.html
Zdjęcie w nagłówku pochodzi z serwisu unsplash.com. Jego autorem jest: Jason Leung
0 Komentarzy