Svaren här känns lite onödigt konfrontativa. TS förstår uppenbarligen inte hur Git fungerar men det är inte särskilt hjälpsamt för någon att konstatera det utan att på något sätt hänvisa till vilken kunskap som saknas. Så här kommer jag med det långa svaret som ingen egentligen bad om, men jag känner behövs.
Först måste vi få ett grepp om varför frågan "hur tar jag bort en fil från en commit" inte egentligen är vettig rent tekniskt, för det är i praktiken omöjligt. En commit i Git är "immutable", den går inte att ändra på. Det är en mycket viktig hörnsten i hur Git fungerar.
Git är en form av filsystem, men istället för att vara uppbygt på att du hittar filer baserat på deras namn så hittar du istället filer baserat på deras innehåll. Det är s.k. content-addressable. Detta tar sin form i att namnet på en fil i Gits objektsdatabas (ett Git-objekt) helt enkelt är en hash av innehållet i filen. Det betyder också att innehållet i ett Git-objekt inte kan ändras, för ändrar du innehållet får du en ny hash, och då är det inte längra samma objekt.
De tre mest fundamentala Git-objekten är commit, tree och blob. En commit innehåller bland annat ett meddelande och en referens till det tree (Gits motsvarighet till en mapp) som utgör projektets rot vid det tillfället committen gjordes. Ett tree i sin tur kan referera till andra trees (tänk nästlade mappar) eller till blobbar (Gits motsvarighet till filer). Referenser utgörs av de refererade objektens hashar, och eftersom det finns en obruten kedja av refererande hashar från committen till varenda blob (fil) så går det inte att ändra varken innehåll i filer eller mappar utan att committens hash måste ändras; vilket per definition innebär att det blir en ny commit.
För att förstå detta lite tydligare kan vi ta ett enkelt exempel: ett projekt med filen "README.md" med texten "Hej!" samt samma fil kompilerad till en PDF.
project
├── README.md
└── README.pdf
Vi antar att det finns en enstaka commit med meddelandet "Add README.md" där syftet var att lägga till endast README.md, men av misstag lades även README.pdf till. Då får vi en objektsdatabas som kan visualiseras på följande vis:
Det faktiska innehållet i commit och tree är följande (går att få ut med `git cat-file -p <hash>`):
commit:
tree 0f4e1542ed3ee409c58723d8f20fd00adc7c407d
author SimpLar <example@example.com> 1689351051 +0200
committer SimpLar <example@example.com> 1689351051 +0200
Add README.md
tree:
100644 blob 3167944bbfb3218d11beb17b4154a8c1488db0a4 README.md
100644 blob a65089c6bea53541e648e04722fa71839e34ef36 README.pdf
Blobbarna är bara innehållet i filerna plus lite metadata.
Så om vi leker med tanken att ta bort README.pdf från denna commit, ja då måste ju den andra raden i trädet gå väck. Men om vi tar bort den raden ändrar vi trädets innehåll, och därmed ändrar vi hashen och det är inte längre samma träd så vi måste skapa ett nytt. Då måste vi referera till ett nytt träd i committen, men då ändrar vi committens innehåll och därmed dess hash, och då är det likväl inte längre samma commit.
Slutsats: det går inte att på något sätt ändra på en commit
Så vad menar folk då egentligen när de säger "ta bort en fil från en commit" eller "ändra en commit"? Kort sagt menar de att skapa en ny commit. Det kan vi exempelvis göra med "git commit --amend". Exempelvis på detta vis:
$ git rm README.pdf
$ git commit --amend --no-edit
För det otränade ögat ändrar detta senaste committen så att README.pdf inte längre är en del av den. Men det som i själva verket händer är att vi skapar en ny commit och pekar om branchen på den nya.
Notera att vi nu har en ny commit som refererar till ett nytt tree som endast refererar till README.md. Vi har inte ändrat en existerande commit, utan bara skapat en ny. Detta är inga som helst problem om du bara har ett lokalt repo, men så fort du har en remote någonstans blir det jobbigare för du får endast pusha en branch om den kommit du försöker pusha upp är en strikt efterträdare till den senaste committen som finns på remoten (såvida du inte använder "--force", eller lite säkrare, "--force-with-lease", i vilket fall Git skriver över remote-branchen).
Hur vet man att en commit X är en efterträdare till en commit Y? Bra fråga: en commit refererar alltid till den föregående committen. Vi kan skriva till en rad "Då!" i README.md och committa det.
Den nya committen ser ut såhär:
tree da67510a03ff51cc88d74b5a1af5361eb3e2ca27
parent 85b11b7a4166669814c2c46092026f425d4d999b
author SimpLar <example@example.com> 1689351995 +0200
committer SimpLar <example@example.com> 1689351995 +0200
Add new line
Den har en "parent"-referens, den föregående committens hash. Och här har vi ett ännu större krux: om du vill "ändra" i en commit X som ligger flera commits tillbaka, ja då måste du inte bara skapa en ny commit, utan även varje commit som följer. Du har ju effektivt skapat en ny commit X' med en ny hash, och följande commit måste då ha en ny parent-referens vilket givetvis ändrar dennes innehåll och hash, och effekten fortsätter.
Summa summarum ändrar du inte en commit, utan du skriver om historiken. Git tillhandahåller ett gäng kommandon för detta, där de jag själv använder flitigast är "rebase" och "--amend"-flaggan till "commit". Förstår man vad dessa gör kan man göra riktigt snitsiga grejer utan att kliva andra på tårna. Förstår man inte vad de gör kan det gå rätt illa rätt fort.
Det här tog mig typ en timme att skriva. Och jag som skulle ha spelat Gran Turismo ikväll. Hoppas det är till någon hjälp.