Hogyan javíts ki egy régebbi commitot a Git historyban?

A Git egy rendkívül erős verziókezelő rendszer, amely szabadságot ad a fejlesztőknek arra, hogy nyomon kövessék, kezeljék és szükség esetén módosítsák a projektjük fejlődését. Bár a Git történelem lineáris és megváltoztathatatlannak tűnhet, valójában számos eszközt kínál a korábbi commitok finomhangolására vagy akár jelentős átalakítására. De miért is akarnánk belenyúlni a múltba? Lehet, hogy egy elírás csúszott az üzenetbe, egy félkész funkció került fel túl korán, egy érzékeny adat (pl. jelszó) landolt véletlenül a kódbázisban, vagy egyszerűen csak rendezettebb, áttekinthetőbb történetet szeretnénk létrehozni a későbbi elemzések vagy a kollégák számára.

Azonban ez a hatalom felelősséggel jár. A Git történelem átírása komoly következményekkel járhat, különösen megosztott, publikus ágakon. Ez a cikk egy átfogó útmutatót kínál a Git történelem módosítására szolgáló leggyakoribb és leghatékonyabb eszközökhöz, kitérve a biztonságos használatukra és a legfontosabb megfontolásokra.

1. Az Azonnali Javítás: git commit --amend

Kezdjük a legegyszerűbb esettel: amikor az utoljára létrehozott commitban szeretnénk valamit javítani. Talán elírtunk valamit az üzenetben, vagy elfelejtettünk hozzáadni egy fájlt a commitolás előtt. Ilyenkor a git commit --amend parancs a legjobb barátunk.

Mire jó?

  • Az utolsó commit üzenetének módosítására.
  • Az utolsó commit tartalmának módosítására (új fájlok hozzáadása, meglévők megváltoztatása).

Hogyan működik?

  1. Ha csak az üzenetet akarjuk módosítani, egyszerűen futtassuk: git commit --amend. Ekkor megnyílik a konfigurált szövegszerkesztőnk a commit üzenettel, amit módosíthatunk.
  2. Ha a commit tartalmát is módosítani akarjuk (azaz fájlokat akarunk hozzáadni vagy eltávolítani a commitból), először tegyük meg a szükséges változtatásokat a munkakönyvtárban, majd adjuk hozzá azokat a stagelt területhez (git add <fájl>). Ezután futtassuk a git commit --amend parancsot. A Git ekkor az előző commitot fogja felváltani egy újjal, amely tartalmazza a most hozzáadott változtatásokat, és az eredeti commit üzenetet.

Fontos: A --amend valójában egy teljesen új commitot hoz létre, amely felváltja az előzőt. Ez azt jelenti, hogy az előző commit hash-je megváltozik. Ha az előző commitot már feltoltuk egy távoli repository-ba, és valaki más már letöltötte, akkor a történelem felülírása konfliktusokhoz vezethet (erről bővebben később).

2. A Svájci Bicska: git rebase -i (Interaktív Rebase)

Ha a módosítani kívánt commit nem az utolsó a történelemben, akkor a git rebase -i, vagyis az interaktív rebase a leggyakoribb és legerősebb eszközünk. Ez a parancs lehetővé teszi számunkra, hogy átírjuk a commitok sorrendjét, módosítsuk az üzenetüket, egyesítsük vagy felosszunk commitokat, sőt, akár töröljük is őket.

Mi az interaktív rebase?

Az interaktív rebase lényegében lehetővé teszi, hogy „visszautazzunk az időben” egy bizonyos commitig, majd onnantól kezdve sorról sorra újraépítsük a történetet, lehetőséget adva a módosításokra minden egyes commitnál. Ehhez meg kell adnunk egy „határt”, egy commitot, amelytől kezdve a történetet újra akarjuk írni. Ez a commit maga *nem* kerül felülírásra, hanem az azt követő commitok lesznek feldolgozva.

Hogyan indítjuk?

A leggyakoribb módja a git rebase -i indításának, ha megadjuk, hogy hány commitot akarunk visszamenni a HEAD-től (az aktuális pozíciótól). Például, ha az utolsó 3 commitot akarjuk szerkeszteni:

git rebase -i HEAD~3

Vagy ha egy konkrét commit hash-ét ismerjük, és az azutáni commitokat akarjuk szerkeszteni:

git rebase -i <commit-hash>

A parancs futtatásakor a Git megnyit egy szövegszerkesztőt, amely felsorolja a kiválasztott commitokat, a legkorábbitól a legújabbig, és mindegyik előtt egy kulcsszót (alapértelmezetten pick). Ezen a ponton adhatjuk meg a Gitnek, hogy mit tegyen az egyes commitokkal:

  • p, pick = A commit használata
  • r, reword = A commit használata, de az üzenet szerkesztése
  • e, edit = A commit használata, de leállás a változtatások elvégzéséhez
  • s, squash = A commit használata, de az előző commitba egyesítése
  • f, fixup = Hasonló a squash-hoz, de elveti a commit üzenetét
  • x, exec = Egy shell parancs futtatása
  • d, drop = A commit eltávolítása
  • m, merge = Hasonló a pick-hez, de megőrzi az eredeti merge üzenetet

2.1. Commit Üzenet Módosítása (reword)

Ez az egyik leggyakoribb oka az interaktív rebase használatának. Egy elírt, félreérthető vagy egyszerűen csak nem megfelelő commit üzenet könnyen javítható.

Mikor használd? Amikor egy commit üzenete pontatlan vagy nem felel meg a projekt szabályainak.

Példa lépésről lépésre:

  1. Indítsd az interaktív rebase-t a megfelelő számú commitra (pl. git rebase -i HEAD~3).
  2. A megnyílt szerkesztőben találd meg a módosítani kívánt commitot. Cseréld le a pick szót reword-re (vagy r-re) az adott sor elején.
    reword <commit-hash> commit: Elrontott uzenet
    pick <commit-hash> commit: Masodik commit
    pick <commit-hash> commit: Harmadik commit
  3. Mentsd el és zárd be a szerkesztőt.
  4. A Git ekkor megnyit egy újabb szerkesztőt, amelyben az eredeti commit üzenet található. Itt módosítsd az üzenetet a kívánt módon.
  5. Mentsd el és zárd be újra a szerkesztőt.

A Git végrehajtja a rebase-t, és az új commit üzenettel megjelenik a történelemben.

2.2. Commit Tartalmának Módosítása (edit)

Néha nem csak az üzenet rossz, hanem magában a commitban is hibák vannak, vagy valami hiányzik belőle.

Mikor használd? Amikor egy commitban lévő fájlokat kell módosítani, hozzáadni vagy eltávolítani.

Példa lépésről lépésre:

  1. Indítsd az interaktív rebase-t (pl. git rebase -i HEAD~3).
  2. Cseréld le a pick szót edit-re (vagy e-re) a módosítani kívánt commit sora elején.
    edit <commit-hash> commit: Elrontott tartalom
    pick <commit-hash> commit: Masodik commit
    pick <commit-hash> commit: Harmadik commit
  3. Mentsd el és zárd be a szerkesztőt.
  4. A Git leáll a kiválasztott commitnál, és kiírja, hogy „Stopped at <commit-hash>… You can amend the commit now”. Ezen a ponton a munkakönyvtárad az adott commit állapotába kerül.
  5. Végezd el a szükséges változtatásokat a fájlokon (pl. javítsd a hibát, adj hozzá egy elfelejtett sort, törölj egy fájlt).
  6. Add hozzá a módosított fájlokat a stagelt területhez: git add <fájl>.
  7. Módosítsd az aktuális commitot a git commit --amend paranccsal (itt akár az üzenetet is szerkesztheted).
  8. Folytasd a rebase-t: git rebase --continue.

A Git ekkor folytatja a történelem újraépítését a módosított committal.

2.3. Commitek Egyesítése (squash és fixup)

Gyakori, hogy fejlesztés közben sok apró, „WIP” (Work In Progress) vagy „fix” commitot készítünk, amelyek önmagukban nem sokat jelentenek. Ezeket érdemes lehet egyesíteni egy nagyobb, összefogottabb committá, hogy a történelem tisztább és áttekinthetőbb legyen.

Mikor használd? Apró javítások, refaktorálások, kiegészítő változtatások egyesítése egy logikusan összefüggő committá.

Mi a különbség squash és fixup között?

  • squash: Egyesíti az aktuális commitot az előzővel. Megnyit egy szerkesztőt, ahol az egyesített commit üzenetét írhatod meg, amely tartalmazza mindkét eredeti commit üzenetét.
  • fixup: Egyesíti az aktuális commitot az előzővel, de elveti az aktuális commit üzenetét, és megtartja az előző commit üzenetét. Ez akkor hasznos, ha az aktuális commit üzenete irreleváns, és csak a változtatásokat akarjuk hozzáfűzni.

Példa lépésről lépésre (squash):

  1. Indítsd az interaktív rebase-t (pl. git rebase -i HEAD~3).
  2. Tegyük fel, hogy van három commitod:
    pick <hash1> feature: Alap funkcionalitas
    pick <hash2> fix: Eliras javitas
    pick <hash3> refactor: Kicsi atszervezes

    A fix: Eliras javitas és a refactor: Kicsi atszervezes commitokat akarjuk az elsőbe egyesíteni.

  3. Módosítsd a fájlt így:
    pick <hash1> feature: Alap funkcionalitas
    squash <hash2> fix: Eliras javitas
    squash <hash3> refactor: Kicsi atszervezes

    Figyeld meg, hogy a squash mindig az előtte lévő commithoz köti magát.

  4. Mentsd el és zárd be a szerkesztőt.
  5. A Git ezután megnyit egy új szerkesztőt, ahol az egyesített commit üzenetét írhatod meg. Láthatod majd mindhárom eredeti commit üzenetét, és eldöntheted, hogy mi legyen a végső, tiszta üzenet.
  6. Mentsd el és zárd be a szerkesztőt.

A történelemben mostantól csak egyetlen commit szerepel majd a három helyett, az általad megadott üzenettel.

2.4. Commitek Törlése (drop)

Néha szükségessé válhat, hogy teljesen eltávolítsunk egy commitot a történelemből. Például egy kísérletezős, félresikerült commit, ami nem illik bele a végső történetbe.

Mikor használd? Felesleges, hibás vagy félresikerült commitok végleges eltávolítására.

Példa lépésről lépésre:

  1. Indítsd az interaktív rebase-t (pl. git rebase -i HEAD~3).
  2. Keresd meg a törölni kívánt commitot, és cseréld le a pick szót drop-ra (vagy d-re) az adott sor elején.
    pick <commit-hash> commit: Elso commit
    drop <commit-hash> commit: Feleleges commit
    pick <commit-hash> commit: Harmadik commit
  3. Mentsd el és zárd be a szerkesztőt.

A Git végrehajtja a rebase-t, és a törölt commit eltűnik a történelemből.

3. Történelem Átírása Nagyméretű, Komplex Esetekben: git filter-branch és git filter-repo

Az interaktív rebase kiválóan alkalmas egy kisebb commit-tartomány módosítására. Azonban ha a történelem egészén keresztül kell nagy volumenű változtatásokat végrehajtani (pl. egy titkos fájl, jelszó eltávolítása az *összes* commitból, egy nagy fájl végleges törlése minden commitból, vagy egy alfoldert külön repository-ba való kiemelése), akkor a git filter-branch vagy a modernebb és ajánlottabb git filter-repo jön képbe.

Mire jók?

Ezek az eszközök a teljes repository történetén végigmennek, és minden egyes commitot a megadott szabályok szerint módosítanak. Rendkívül erősek, de ugyanilyen veszélyesek is, ha nem megfelelően használják őket. A git filter-branch egy régebbi, beépített parancs, de sok esetben lassú és bonyolult a használata. A git filter-repo egy harmadik féltől származó eszköz (pip install git-filter-repo), amely sokkal gyorsabb, felhasználóbarátabb és ajánlottabb a legtöbb esetben.

Példa (git filter-repo-val egy fájl eltávolítása a teljes történelemből):

git filter-repo --path <fájlnév> --invert-paths

Ez a parancs eltávolítja a megadott fájlt az összes commitból, mintha sosem létezett volna. Ez drámaian megváltoztatja a repository minden commitjának hash-jét.

Fontos figyelmeztetés: Ezeket az eszközöket csak akkor használd, ha pontosan tudod, mit csinálsz, és megérted a következményeit. Mindig készíts biztonsági másolatot a repository-ról, mielőtt ilyen jellegű műveleteket hajtasz végre!

4. Fontos Megfontolások és Biztonsági Intézkedések

A Git történelem átírása egy erőteljes képesség, amelyet felelősségteljesen kell használni. Íme a legfontosabb szempontok, amelyeket figyelembe kell venni:

A „Golden Rule”: Soha Ne Írd Újra a Megosztott Történelemét!

Ez a legfontosabb szabály. Ha már feltoltál (git push) egy commitot egy távoli repository-ba, és mások is letöltötték (git pull), akkor az a commit a „megosztott történelem” részévé vált. Ha utána felülírod azt a commitot lokálisan, majd megpróbálod újra feltolni, az konfliktusokat okoz, és a kollégáknak bonyolult lépéseket kell tenniük (pl. klónozniuk kell újra a repository-t, vagy agresszív rebase-t kell végrehajtaniuk), hogy szinkronba kerüljenek veled. Ez összezavarhatja a csapatot, és adatvesztéshez vezethet.

Kivétel: Ha egy privát ágon dolgozol, amit még nem osztottál meg, vagy ha egy PR (Pull Request) még nyitott, és te vagy az egyetlen, aki azon az ágon dolgozik, akkor biztonságosabb a történelem átírása. De még ekkor is érdemes kommunikálni a csapat többi tagjával.

git push --force vs. git push --force-with-lease

Ha egy lokálisan átírt történetet akarsz feltolni egy távoli repository-ba, akkor kénytelen leszel a git push --force parancsot használni. Azonban ez rendkívül veszélyes, mert felülírja a távoli ág tartalmát, függetlenül attól, hogy az időközben megváltozott-e. Ha valaki más időközben feltolt oda változtatásokat, a te --force push-od felülírja azokat.

A biztonságosabb alternatíva a git push --force-with-lease. Ez a parancs csak akkor engedi meg a felülírást, ha az ág távoli verziója pontosan megegyezik azzal a verzióval, amelyet te utoljára letöltöttél. Így elkerülheted, hogy akaratlanul felülírd mások munkáját.

Mindig Készíts Biztonsági Másolatot!

Mielőtt jelentős történelem-átírási műveletekbe kezdenél, mindig készíts egy biztonsági másolatot. Ezt megteheted egy új ág létrehozásával (git branch backup-branch) vagy akár egy „bare” klónozással (git clone --bare . ../backup-repo.git).

A `git reflog` Ereje

Ha elrontottál valamit, és úgy érzed, elveszettél a Git történelemben, a git reflog parancs a megmentőd. Ez a parancs megmutatja az összes mozgást a HEAD pointeren, beleértve a commitokat, rebase-eket, reseteket. Ez lehetővé teszi, hogy visszatérj egy korábbi, működőképes állapotba, még akkor is, ha az a commit már nem része a „hivatalos” történelemnek. Ha például elrontottad a rebase-t, a git reset --hard HEAD@{n} (ahol n egy reflog bejegyzés sorszáma) paranccsal visszaugorhatsz egy korábbi állapotba.

Összegzés

A Git történelemének módosítása egy hatalmas és hasznos képesség, amely lehetővé teszi a fejlesztők számára, hogy tiszta, áttekinthető és informatív commit történetet tartsanak fenn. A git commit --amend az utolsó commit gyors javítására szolgál, míg a git rebase -i a legfontosabb eszköz a korábbi commitok finomhangolására – üzenetek szerkesztésére, tartalmak módosítására, commitok egyesítésére vagy törlésére. A git filter-branch és git filter-repo pedig a nagyszabású, repository-szintű történelem-átírásra alkalmasak.

Fontos azonban emlékezni, hogy ez a képesség felelősséggel jár. A legfontosabb szabály, hogy soha ne írd újra a publikált, megosztott ágak történetét. Mindig légy óvatos, készíts biztonsági másolatot, és használd a git push --force-with-lease parancsot, ha muszáj feltolnod egy átírt történetet. A reflog pedig mindig ott van, hogy kihúzzon a bajból.

Gyakorlással és kellő óvatossággal a Git történelemének mestere leszel, és a csapatod is hálás lesz az átlátható és rendezett munkádért.

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük