Cofanie błędnego merge’a w git-svn

Kolejny wpis oparty na ostatnich doświadczeniach z pracy. Tym razem dość klasycznie: stworzyłem w swoim repozytorium git feature brancha na podstawie brancha testing zamiast mastera. Do testing – jak łatwo się domyślić – domerdżowuję feature branche przed przekazaniem ich do testów, w związku z tym jest tam zawsze co najmniej kilka modyfikacji czekających na przetestowanie.

Niestety, zorientowałem się o tym dopiero po merge’u ww. feature brancha do mastera oraz wykonaniu git svn dcommit  (korzystam z git-svn), więc całość została nie tylko u mnie, ale też poszła do SVN-a. Sam git reset do commita sprzed merge’owania niewiele pomógł, ponieważ każdy git svn rebase i tak wracał do pierwotnego ustawienia HEAD.

Na początek, żeby pozbyć się nieprzetestowanych zmian z repozytorium SVN-a, wygenerowałem odwrotnego diffa:

git diff --no-prefix HEAD HEAD~1 > ~/revert.diff

I spatchowałem kod w masterze:

patch -p0 < ~/revert.diff

Po czym dodałem i scommitowałem zmiany w masterze oraz wypchnąłem je do SVN-a przez:

git svn dcommit

(można to było zrobić poprzez git reset lub git revert, jednak to był sposób, z którego mogłem skorzystać bez przeglądania dokumentacji, a ciągle istniało ryzyko, że ktoś ten kod w międzyczasie pobierze)

Mając stan SVN-a sprzed błędnego merge’a, przeszedłem do wertowania internetu.

Rozwiązaniem okazało się cofnięcie HEAD w gicie do ostatniego commita sprzed merge’a:

git reset --hard commit_hash

A następnie wykonanie tego samego dla SVN-owego remote’a. W tym celu należy użyć

git svn reset -r svn_revision_id

gdzie svn_revision_id  to ID ostatniej rewizji SVN sprzed błędnego merge’a.

Dzięki temu zarówno repozytorium git, jak i lokalny remote wskazują na stan sprzed merge’a. Wykonanie:

git svn rebase

pobierze więc kolejne commity z SVN na nowo i nie będzie przestawiało HEAD-a w niepożądane miejsce.

Z racji tego, że mój feature branch posiadał błędnego rodzica, do przeniesienia zmian na mastera skorzystałem z cherry-pick:

git cherry-pick commit_hash

 

 

git-svn – pracuj lokalnie z git, synchronizuj się z SVN

Wszystkie projekty w mojej pracy są wersjonowane mocno na SVN. Ten system kontroli wersji ma już swoje lata i już dawno temu zaczął być wypierany przez gita. Niestety, u nas perspektyw na migrację nie ma w najbliższej przyszłości, więc od kilku lat korzystałem bezpośrednio z SVN.

Na początku niezbyt mi to przeszkadzało – najpierw się wdrażałem, potem zmieniłem zespół, potem zmiany wprowadzałem liniowo i nie było zapotrzebowania na „skakanie” pomiędzy zagadnieniami. Jedynie czasem trzeba było wybrać część zmian z wielu innych w tych samych plikach, ale mniej więcej w tym czasie odkryłem możliwości pary diff + patch, przez co wybrane zmiany można było łatwo scommitować gdzieś na boku bez zbędnego komentowania zmian, które wejść nie powinny.

Sytuacja zmieniła się radykalnie wraz ze zmianami w szeregach naszego biznesu. Coraz częściej wykonywaliśmy po kilka zagadnień „jednocześnie”, przestała mieć też znaczenie kolejność wykonywania zmian – czasem zagadnienia skończone jednego dnia musiały wejść na produkcję jeszcze w tym samym dniu, inne zaś musiały czekać tygodniami, a czasem nawet zostawały zaorane.

Miało to co najmniej trzy poważne konsekwencje:

  • wiecznie zapchane working copy: po roku takiej pracy miałem zawsze kilkadziesiąt zmienionych plików i efektywne przejrzenie diffa przed commitem było praktycznie niemożliwe, przez co często zdarzało się zapomnieć o jakimś pliku; nie muszę też pisać, że codzienny widok niewdrożonych zmian sprzed np. kilku tygodni ma katastrofalny wpływ na motywację,
  • utrudnione testy: zmiany bardzo często dotyczyły tych samych plików, codziennością było kilka zagadnień w tym samym module, co powodowało, że testowanie niektórych zmian było bezcelowe w oderwaniu od innych, zaś czasem sztucznie wstrzymywałem pracę nad dalszymi zmianami w oczekiwaniu na testy ze strony biznesu,
  • wieczny ból dupy przed wdrożeniami: po pewnym czasie nakładających się na siebie zmian było tyle, że diff i patch na boku musiał być używany hurtowo; ręczna edycja diffów bywa czasem utrudniona, co w połączeniu z wybieraniem zmian z kilkudziesięciu plików było szalenie czasochłonne; przygotowania do wdrożenia zaczęły być pasmem kombinowania.

Mniej więcej w tym samym czasie zacząłem podchodzić dużo poważniej do kwestii testowania oprogramowania, co wcale nie ułatwiało sytuacji. SVN był w takich sytuacjach po prostu bezbronny – jego branche w tak szybkim i zmiennym procesie rozwoju oprogramowania nie mają prawa bytu i są strasznie ciężkie. Zacząłem czytać coraz więcej o gicie.

Gita znałem jeszcze z czasów swojej freelancerki, jednak z racji tego, że pracowałem sam, to tak naprawdę go nie znałem. Nie korzystałem nawet aktywnie z branchów – wszystkie moje projekty z tamtego czasu to jedynie master, służący jako kopia zapasowa i archiwum zmian. Wiedziałem jednak, że możliwości są dużo większe, aż w końcu trafiłem na sposób pogodzenia SVN i git – projekt git-svn.

Po przejściu przez kilka tutoriali wiedziałem, że to będzie to. Git-svn pozwala stworzyć gitowe repozytorium na podstawie brancha z SVN, który będzie synchronizowany w dwie strony.

Użyłem więc git-svn do clone’a z SVN. Po kilkudziesięciu minutach mielenia (nasz projekt miał wtedy ponad 2,5k commitów) miałem już gotowe repo, które spatchowałem niescommitowanymi zmianami z kopii roboczej SVN, której dotąd używałem.

Zacząłem ostrożnie – dalej miałem mnóstwo niescommitowanych zmian, dalej nie używałem branchy, jednak git add –patch i selektywne dodawanie zmian do scommitowania w obrębie jednego pliku zrobił furorę – to był ten moment, w którym przestałem zaprzątać sobie głowę myśleniem o wtórnych problemach: czyli „jak tym razem scommitować zmiany wymieszane pomiędzy zagadnieniami?”.

Po miesiącu takiej pracy okazało się, że w zasadzie nie występują większe problemy. Dlaczego więc nie pójść o krok dalej? Tym krokiem było oczywiście przeniesienie zmian do osobnych gałęzi zgodnie z git flow. Dzięki temu rozwiązałem raz na zawsze problem zapchanego working copy (czy – w przypadku gita – indeksu).

Podaję materiały, które mogą się przydać każdemu znajdującemu się w sytuacji podobnej do mojej, a z których sam korzystałem:

8.1 Git and Other Systems – Git and Subversion

Effectively Using Git With Subversion

Na koniec moje protipy:

  • unikaj rebase na synchronizowanym z SVN branchu (przeważnie master), przepisanie historii commitów powoduje czasem zaburzenie liniowości historii, przez co synchronizacja się nie udaje; sam rozwiązuję to w ten sposób, że feature branche merge’uję do mastera z przełącznikiem –no-ff,
  • pamiętaj, że korzystając z git-svn wszelkie zmiany w lokalnych branchach nie są wypychane tak, jak przy „tradycyjnym” repozytorium gita, posiadasz więc prawdopodobnie jedyną kopię tych zmian – pamiętaj o wykonywaniu kopii zapasowych, w przypadku uszkodzenia lokalnego repozytorium możesz stracić wszystkie efekty swojej pracy.

Muszę też ostrzec potencjalnych użytkowników – spotkałem się z wieloma opiniami mówiącymi o tym, że git-svn tak bardzo usprawnia pracę, że jest pierwszym krokiem do całkowitego przejścia na gita w pracy. Sprawdziło się to i u mnie – narzędziem tym zaraziłem kolegę z zespołu. Czas na resztę. 😉

Uruchomienie starego kodu na nowych wersjach jQuery

Jeżeli w swoich projektach masz dużo legacy kodu napisanego w JS z użyciem starych wersji biblioteki jQuery, a nie chcesz/nie możesz w danej chwili przepisać jego części niedziałających w nowszych wersjach – polecam zacząć migrację od pluginu jquery-migrate.

Plugin ten wykrywa i przywraca usunięte od wersji 1.9 funkcje jQuery, dzięki czemu stary kod wykona się poprawnie na nowych, niekompatybilnych wstecz wersjach biblioteki.

Oczywiście podpięcie go powinno być stanem przejściowym do czasu pełnej migracji i przepisania wywołań usuniętych funkcji. Sam plugin doskonale w tym pomaga dzięki możliwości generowania ostrzeżeń przy każdym wywołaniu nieobsługiwanej w nowej wersji funkcji. Można dzięki temu korzystać natychmiast z nowych wersji jQuery, stopniowo przepisując stary kod na podstawie zaraportowanych wywołań.

SQL savepoint – obsługa złożonych transakcji

Jakiś czas temu natknąłem się w pracy na problem obsługi złożonych transakcji.

Jako, iż SQL (a przynajmniej DBMS, z którego korzystamy) nie obsługuje zagnieżdżonych transakcji, do tej pory – dla wygody – wystarczała nam prosta modyfikacja adaptera bazodanowego, która pozwalała na „zagnieżdżanie transakcji”, a polegała jedynie na zliczaniu żądań rozpoczęcia transakcji i pilnowaniu liczby wywołań commitów/rollbacków na adapterze. W przypadku niezgodności rzeczywista transakcja (otwierana przy pierwszym żądaniu) nie była commitowana, podobnie, gdy wystąpiło przynajmniej jedno wywołanie rollback na adapterze – żadne zmiany nie były stosowane.

W rzadkich sytuacjach nietypowych, gdzie część zapytań miała zostać zastosowana, a część – w razie potrzeby – nie, obsługa odbywała się w kilku niezależnych transakcjach.

Jednak w końcu przyszedł ten moment, gdy rozbicie na wiele transakcji było praktycznie niemożliwe ze względu na złożoność procesu, który trzeba było zmodyfikować.

W skrajnym przypadku mogło to wyglądać tak:

  1. Początek procesu – wykonanie podstawowych zapytań INSERT/UPDATE
  2. Wykonanie serii zapytań INSERT/UPDATE z możliwością warunkowego ich cofnięcia.
  3. Zakończenie procesu – kolejne zapytania INSERT UPDATE.

W pewnych warunkach powinny być więc zastosowane zmiany z punku 1., cofnięte wszystkie z punktu drugiego i zastosowane zapytania z punktu trzeciego.

I tutaj przydatna okazała się klauzula SAVEPOINT, o której istnieniu wcześniej nie wiedziałem, natomiast idealnie wpasowywała się w tego typu sytuacje.

Jej zasada działania jest banalnie prosta. W momencie wywołania:

SAVEPOINT nazwa_savepointa

wewnątrz transakcji tworzony jest punkt, po którym następują kolejne zapytania i do którego można – w razie potrzeby – cofnąć się poprzez

ROLLBACK TO nazwa_savepointa

W ten sposób „zapominamy” o wykonanych po utworzeniu savepointa zapytaniach SQL, natomiast wszystkie zapytania sprzed tego czasu zostaną zastosowane, jeżeli na całej transakcji zostanie wykonany COMMIT.