Git, Branches und Pull Requests — so arbeiten Teams
Von git init bis push und pull, .venv und .env mit .gitignore heraushalten, kleine Feature-Branches, saubere Commit-Messages und Pull Requests — dazu ein Python-Werkzeug, das git log liest und Commits prüft.
Wie möchtest du lernen?
Das Problem, das wir lösen
Ein KI-Engineer hat eine Woche lang an einem Projekt zur Ticket-Triage gearbeitet: den Prompt geändert, die Keyword-Liste erweitert, eine Funktion neu geschrieben. Am Freitag sind die Ergebnisse schlechter als am Montag. Welche Änderung ist schuld? Der Ordner enthält triage.py, triage_v2.py, triage_final.py und triage_final_REAL.py — und niemand weiß, was sich wo geändert hat.
Gleichzeitig hat ein Teammitglied, das dieselbe Datei in einem geteilten Ordner bearbeitet, die eigene Arbeit überschrieben. Und vor einem Monat hat jemand einen API-Schlüssel für einen schnellen Test direkt in den Code geschrieben. Über den geteilten Ordner ist er inzwischen bei zehn weiteren Leuten gelandet.
Alle drei Probleme haben dieselbe Antwort, und fast jedes Software-Team der Welt nutzt sie: Historie mit Git festhalten, jede Aufgabe in einem eigenen Branch erledigen und, bevor etwas in den Hauptcode kommt, von jemand anderem in einem Pull Request prüfen lassen. Dieses Kapitel lehrt den ganzen Zyklus — und endet damit, ein Python-Werkzeug zu schreiben, das eine der Aufgaben des Reviewers übernimmt.
Am Ende dieses Kapitels kannst du
- Die Historie eines Projekts mit
git init,add,commit,pushundpullfesthalten und mit anderen teilen .venvund.envmit.gitignoreaus dem Repository heraushalten und wissen, was zu tun ist, wenn doch eines davon committet wurde- Kleine, kurzlebige Feature-Branches anlegen und einen sauberen Pull Request öffnen
- Commit-Messages so schreiben, wie Reviewer sie lesen
- Ein Python-Werkzeug schreiben, das
git logübersubprocessliest und Commit-Messages prüft
Was du vorher brauchst: Der AI-Engineer-Track und Entwicklungsumgebungen mit uv — besonders uv init und uv run.
Ersteinrichtung
Git hält fest, wer jeden Commit gemacht hat. Also stellst du dich beim ersten Mal einmal vor. Es lohnt sich außerdem, Git zu sagen, dass der Hauptbranch neuer Repositories main heißen soll, denn so heißt er bei den meisten Teams inzwischen:
git config --global user.name "Nadia Karim"
git config --global user.email "[email protected]"
git config --global init.defaultBranch mainNimm deinen eigenen Namen und die E-Mail-Adresse, mit der dein Konto bei der Code-Plattform registriert ist. Ohne die letzte Zeile nennen ältere Git-Versionen den Hauptbranch master — das funktioniert, passt aber nicht zum Rest des Teams.
Drei Orte: Arbeitsverzeichnis, Staging-Area und Historie
Im vorherigen Kapitel hat uv init ein Git-Repository von selbst angelegt. Das Beispielprojekt in diesem Kapitel beginnt genauso:
uv init ticket-triage --python 3.12
cd ticket-triageGit denkt in drei Orten, und jeder Befehl verschiebt etwas von einem zum anderen:
- Das Arbeitsverzeichnis — dein Ordner, in dem du Dateien bearbeitest
- Die Staging-Area — die Liste dessen, was in den nächsten Commit kommt;
git addlegt Dinge hier ab - Die Historie — die Kette aus Commits;
git commitmacht aus der Staging-Area einen dauerhaften Commit
Warum ein eigener Staging-Schritt? Weil du mitten in einer Aufgabe vielleicht fünf Dateien geändert hast, zwei davon einen Bug beheben und drei ein Feature hinzufügen. Mit dem Staging kannst du sie in zwei getrennte, sinnvolle Commits aufteilen — und das ist für einen Reviewer sehr viel wert.
Den aktuellen Stand siehst du mit git status oder in der Kurzform git status --short.
.gitignore: was nie ins Repository kommt
KI-Projekte haben fast immer eine .env-Datei mit API-Schlüsseln. Angenommen, das Projekt bekommt eine Datei wie diese (sk-... ist hier nur ein Platzhalter, kein echter Schlüssel):
LLM_API_KEY=sk-...Die .gitignore, die uv init anlegt, enthält .venv, aber nicht .env. Deshalb ist der erste Schritt, .env ganz unten einzutragen:
# Secrets
.envFühre jetzt einmal uv run main.py aus (damit .venv und uv.lock entstehen) und prüfe den Stand:
git status --short
?? .gitignore
?? .python-version
?? README.md
?? main.py
?? pyproject.toml
?? uv.lock?? heißt: Git sieht die Datei, verfolgt sie aber noch nicht. Die eigentliche Neuigkeit steht in dem, was nicht in der Liste steht: kein .venv, kein .env. Beide liegen im Ordner, aber Git tut so, als würde es sie nicht sehen. (.venv hat sogar zwei Schutzschichten: Wenn uv die Umgebung anlegt, legt es darin zusätzlich eine winzige .gitignore ab, die nur * enthält.) Und uv.lock ist da — es sollte committet werden, denn darüber bekommen Kolleginnen und Kollegen genau dieselbe Umgebung.
Woher sollen sie dann wissen, welche Schlüssel sie brauchen? Üblich ist, eine Datei .env.example zu committen, die die Namen enthält, aber nicht die Werte. Wer neu dazukommt, kopiert sie nach .env und trägt die eigenen Schlüssel ein.
Ein committetes Geheimnis verschwindet nicht, wenn man es im nächsten Commit löscht. Git merkt sich gerade jeden früheren Zustand, also bleibt der Schlüssel für immer im früheren Commit — jeder kann ihn mitgit showherausholen. Sobald ein Schlüssel auf irgendein Remote gepusht wurde, bleibt nur ein sicherer Schritt: zum Dienst gehen, dem der Schlüssel gehört, ihn widerrufen und einen neuen anlegen. Deshalb muss.envin.gitignorestehen, bevor die Datei überhaupt angelegt wird.
Der erste Commit und gute Commit-Messages
git add .
git commit -m "chore: scaffold project with uv"
[main (root-commit) 0f78e59] chore: scaffold project with uv
6 files changed, 35 insertions(+)
create mode 100644 .gitignore
create mode 100644 .python-version
create mode 100644 README.md
create mode 100644 main.py
create mode 100644 pyproject.toml
create mode 100644 uv.lock0f78e59 ist die Identität des Commits (der Anfang seines Hashes) — auf deinem Rechner wird er anders aussehen, weil Zeitpunkt und Autor mit hineingerechnet werden.
Eine Commit-Message schreibst du für einen künftigen Leser — dich in sechs Monaten und den Reviewer von heute. Viele Teams folgen einem gemeinsamen Stil namens Conventional Commits:
type(scope): short summary in the imperative- type sagt, welche Art von Änderung es ist:
feat(eine neue Fähigkeit),fix(eine Fehlerbehebung),docs,refactor,test,chore(Wartung) - scope ist optional, in Klammern, und nennt den Teil des Projekts:
feat(triage): ... - summary steht im Imperativ („add“, „fix“, „remove“ — nicht „added“), hat höchstens 72 Zeichen und endet ohne Punkt
Nebeneinander sieht der Unterschied eindeutig aus:
wip
fixed stuff
Fixed the bug where priority returned normal for tickets that say OUTAGE in capitals
fix(triage): lowercase ticket text before keyword matchingDie ersten drei lösen keinen Fehler aus — Git akzeptiert jede Nachricht. Aber in git log sagen die ersten beiden nichts, und die dritte ist so lang, dass sie in einer einzeiligen Liste abgeschnitten wird. Die letzte liest ein Reviewer und weiß sofort, was sich geändert hat und wo. Braucht das „Warum“ mehr Platz, gehört es in den Body der Nachricht, nach einer Leerzeile.
Branches: jede Aufgabe in ihrem eigenen Raum
Der main-Branch ist das Wertvollste im Team: Was dort liegt, funktioniert und kann jederzeit ausgeliefert werden. Deshalb arbeitet niemand direkt auf main. Jede Aufgabe bekommt einen neuen Branch:
git switch -c feat/priority-score
Switched to a new branch 'feat/priority-score'Hier wird triage.py geschrieben:
def priority(text: str) -> str:
urgent_words = ("outage", "down", "breach")
if any(word in text for word in urgent_words):
return "high"
return "normal"git add triage.py
git commit -m "feat(triage): add keyword-based priority"
[feat/priority-score ded4456] feat(triage): add keyword-based priority
1 file changed, 5 insertions(+)
create mode 100644 triage.pyBranch-Namen folgen demselben Muster: feat/..., fix/..., docs/... — an der Liste siehst du auf einen Blick, was jeder davon ist.
Ein Wort zur Branching-Strategie. Früher hatten viele Teams mehrere langlebige Branches — develop, release, hotfix — und verschoben Code nach aufwendigen Regeln zwischen ihnen. Die meisten Teams gehen heute den einfacheren Weg: ein main und kleine, kurzlebige Feature-Branches davon, die innerhalb von ein bis zwei Tagen reviewt und zurückgemergt werden. Je länger ein Branch getrennt bleibt, desto weiter entfernt sich main, und desto größer werden die Konflikte, wenn er zurückkommt. Dieser Kurs geht den einfachen Weg.
Remotes: push und pull
Bisher liegt alles auf deinem Rechner. Für die Zusammenarbeit im Team braucht man ein Remote-Repository — meistens auf GitHub oder einem ähnlichen Dienst. Legst du dort ein Repository an, bekommst du eine Adresse, und du schreibst:
git remote add origin https://github.com/<your-account>/ticket-triage.gitDie Ausgaben in diesem Kapitel wurden ohne Online-Dienst überprüft: Ein „bare“-Repository wurde in einem Nachbarordner angelegt und als Remote genutzt. Für Git sind beide dasselbe, und so kannst du auch auf deinem eigenen Rechner üben:
git init --bare ../remote.git
git remote add origin ../remote.gitDer erste Push braucht -u, um festzulegen, mit welchem Remote-Branch dieser Branch verknüpft ist:
git push -u origin main
branch 'main' set up to track 'origin/main'.
To ../remote.git
* [new branch] main -> mainDer Feature-Branch läuft genauso:
git push -u origin feat/priority-score
branch 'feat/priority-score' set up to track 'origin/feat/priority-score'.
To ../remote.git
* [new branch] feat/priority-score -> feat/priority-scoreJetzt klont ein Teammitglied das Repository:
git clone remote.git teammate
Cloning into 'teammate'...
done.Im Ordner des Teammitglieds liegen README.md, main.py, pyproject.toml, uv.lock (und die versteckten Dateien) — aber kein .venv und kein .env. Genau so soll es sein: Die Person führt uv sync aus, um die eigene Umgebung aufzubauen, und legt die eigene .env aus .env.example an.
Nachdem das Feature in main gemergt wurde (in echter Arbeit passiert das, wenn der Pull Request gemergt wird — siehe unten), holt sich das Teammitglied den neuen Stand:
git pull
Updating 0f78e59..5f802b4
Fast-forward
triage.py | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 triage.py(Die echte Ausgabe beginnt mit einer Zeile From ..., die den vollständigen Pfad des Remotes angibt; sie ist hier weggelassen.) Fast-forward heißt: Das Teammitglied hatte keine eigenen neuen Commits, also hat Git sein main einfach nach vorne gezogen.
Issues und Pull Requests
Im Unternehmen beginnt Arbeit meist mit einem Issue — einer nummerierten Beschreibung eines Problems, geschrieben auf der Code-Plattform. Zum Beispiel: #12 — Großgeschriebene Keywords werden nicht erkannt (Tickets mit „OUTAGE“ landen als normal).
Dann läuft der Zyklus so ab:
- Ein Branch von
main:git switch -c fix/uppercase-keywords - Kleine, sinnvolle Commits
- Den Branch pushen
- Auf der Website des Dienstes oder über die Kommandozeile einen Pull Request (PR) öffnen — eine Anfrage nach dem Muster „Nimm die Änderungen meines Branches in
mainauf“ - Der Reviewer kommentiert, und du antwortest mit neuen Commits auf demselben Branch
- Sobald genehmigt und die automatischen Prüfungen (CI) bestanden sind, wird der PR gemergt und der Branch gelöscht
Mit GitHubs Kommandozeilen-Tool gh sieht Schritt 4 so aus (die Ausgabe hängt von deinem Konto und Repository ab, deshalb ist sie hier nicht gezeigt):
gh pr create --base main --title "fix(triage): detect uppercase keywords" --body-file PR.mdEine saubere PR-Beschreibung (PR.md) beantwortet die drei Fragen des Reviewers — was, warum und wie getestet:
## What
Lowercase ticket text before matching urgent keywords.
## Why
Tickets written in capitals ("OUTAGE") were scored as normal.
Closes #12
## How I tested
priority("Service DOWN") returned "normal" on main, "high" on this branch
uv run check_commits.py -> 0 of 2 commit messages need workDie Zeile Closes #12 ist eine kleine Hilfe: Wird der PR in den Standard-Branch gemergt, schließt der Dienst Issue #12 von selbst.
Worauf Mentor:innen beim Review achten
Jede praktische Aufgabe in diesem Kurs wird mit den Augen eines Reviewers angesehen, und im Unternehmen kommen diese Fragen auf:
- Ist der PR klein und hat nur einen Zweck? Ein Feature, ein Bug oder ein Refactoring — nicht alles zusammen. Ab ein paar hundert Zeilen sinkt die Qualität des Reviews, weil niemand die Aufmerksamkeit über einen langen Diff halten kann.
- Liest sich die Commit-Historie wie eine Geschichte? Eine Historie voller
wipundfixnützt niemandem. - Was ist drin, das nicht drin sein sollte?
.venv,.env,__pycache__, große Datendateien — taucht eines davon im Diff auf, geht der PR zurück. - Was sollte drin sein? Wenn ein neues Paket hinzugekommen ist, müssen die Änderungen an
pyproject.tomlunduv.lockim Diff stehen. - Sagt die Beschreibung, wie getestet wurde?
Ist die Nachricht des letzten Commits falsch, kannst du sie vor dem Push ändern:
git commit --amend -m "docs: describe the project in README"
git log --oneline -1
a18cc1a docs: describe the project in READMEEinen Commit nach dem Push zu ändern heißt, die Historie umzuschreiben, und das macht Kolleginnen und Kollegen Ärger; in dem Fall ist ein neuer Commit besser. Viele Teams mergen PRs mit „Squash“ — alle Commits des Branches werden zu einem zusammengefasst, und der PR-Titel wird zur Nachricht. Dann muss auch der PR-Titel demselben Stil folgen.
Ein vollständiges Beispiel
Eine der Aufgaben des Reviewers lässt sich an eine Maschine abgeben: prüfen, dass jede Commit-Message des Branches den Regeln folgt. Teams lassen solche Prüfungen in der CI laufen, damit das Ergebnis erscheint, sobald ein PR geöffnet wird. Das untenstehende check_commits.py macht genau das — es führt git log aus und liest nur die Commits des aktuellen Branches, also die, die nicht in main sind:
import re
import subprocess
import sys
ALLOWED_TYPES = {"feat", "fix", "docs", "refactor", "test", "chore"}
SUBJECT = re.compile(r"^(?P<type>[a-z]+)(\([a-z0-9-]+\))?: (?P<summary>.+)$")
MAX_LENGTH = 72
VAGUE = {"wip", "fix", "update", "changes", "stuff", "misc"}
def problems_in(subject: str) -> list[str]:
found = []
if subject.strip().lower() in VAGUE:
found.append("too vague for a reviewer")
if len(subject) > MAX_LENGTH:
found.append(f"longer than {MAX_LENGTH} characters ({len(subject)})")
match = SUBJECT.match(subject)
if match is None:
found.append("missing 'type: summary' prefix")
elif match["type"] not in ALLOWED_TYPES:
found.append(f"unknown type '{match['type']}'")
if subject.endswith("."):
found.append("ends with a period")
return found
def branch_subjects(base: str) -> list[str]:
result = subprocess.run(
["git", "log", "--reverse", "--format=%s", f"{base}..HEAD"],
capture_output=True,
encoding="utf-8",
)
if result.returncode != 0:
raise SystemExit(f"git log failed: {result.stderr.strip()}")
return result.stdout.splitlines()
def main() -> int:
subjects = branch_subjects("main")
failures = 0
for subject in subjects:
problems = problems_in(subject)
print(("ok " if not problems else "FAIL") + " " + subject)
for problem in problems:
print(f" - {problem}")
failures += 1 if problems else 0
print(f"{failures} of {len(subjects)} commit messages need work")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())Zum Testen wurde ein Branch von main mit fünf Commits angelegt — manche mit absichtlich schlechten Nachrichten. (--allow-empty erzeugt einen Commit, ohne eine Datei zu ändern; das reicht, um Nachrichten zu prüfen. In echter Arbeit trägt jeder Commit eine Codeänderung.)
git switch -c fix/uppercase-keywords
git commit --allow-empty -m "wip"
git commit --allow-empty -m "Fixed the bug where priority returned normal for tickets that say OUTAGE in capitals"
git commit --allow-empty -m "fix(triage): lowercase text before matching."
git commit --allow-empty -m "feature: add tests"
git commit --allow-empty -m "test(triage): cover uppercase keywords"Dann, innerhalb des Repositorys:
uv run check_commits.pyFAIL wip
- too vague for a reviewer
- missing 'type: summary' prefix
FAIL Fixed the bug where priority returned normal for tickets that say OUTAGE in capitals
- longer than 72 characters (84)
- missing 'type: summary' prefix
FAIL fix(triage): lowercase text before matching.
- ends with a period
FAIL feature: add tests
- unknown type 'feature'
ok test(triage): cover uppercase keywords
4 of 5 commit messages need workDas Programm gibt den Exit-Code 1 zurück, weil mindestens eine Nachricht durchgefallen ist. Ist alles in Ordnung, ist er 0.
Die Entscheidungen in diesem Skript:
main..HEAD — diese Zwei-Punkte-Schreibweise bedeutet „Commits in HEAD, die nicht in main sind“. Also nur die eigene Arbeit dieses Branches. Würde man die ganze Historie prüfen, würde der PR wegen Commits von vor einem Jahr durchfallen, die andere geschrieben haben. --reverse sortiert sie vom ältesten zum neuesten, in der Reihenfolge, in der die Arbeit entstanden ist.
--format=%s — %s bedeutet nur die erste Zeile des Commits (den Betreff). Kein Hash, kein Datum, kein Autor — es gibt also nichts zu parsen; jede Zeile ist eine Nachricht.
encoding="utf-8" — mit text=True verwendet Python die Standardkodierung des Betriebssystems, und die ist unter Windows oft nicht UTF-8. Schreibt jemand eine Commit-Message auf Bengalisch oder mit Emoji, kann die Ausgabe kaputtgehen. Wer die Kodierung angibt, beseitigt diese Unsicherheit.
Eigene Meldung statt check=True — schlägt git fehl (weil das Skript außerhalb eines Repositorys läuft oder es keinen Branch main gibt), zeigt es die eigene Fehlermeldung von git statt eines langen Python-Tracebacks. Übergibt man einen String an SystemExit, gibt Python ihn auf stderr aus und beendet sich mit dem Exit-Code 1.
Der Exit-Code — die CI liest keine Ausgabe; sie schaut nur, ob das Programm mit 0 endete. Also gibt main() eine Zahl zurück, und sys.exit reicht sie an das Betriebssystem weiter.
Wenn es schiefgeht
Author identity unknown gefolgt von fatal: unable to auto-detect email address Git weiß nicht, wessen Namen es in den Commit schreiben soll. Führe die beiden Befehle git config --global user.name ... und user.email ... vom Anfang des Kapitels einmal aus.
error: src refspec main does not match any Es gibt noch keine Commits, also existiert der Branch main eigentlich nicht — es gibt nichts zu pushen. Erst git add und git commit, dann pushen. Dieselbe Meldung erscheint, wenn der Branch tatsächlich master heißt; mit git branch prüfst du, auf welchem Branch du gerade bist.
fatal: The current branch feat/x has no upstream branch. Ein neuer Branch wird zum ersten Mal gepusht, und Git weiß nicht, an welchen Remote-Branch er gehen soll. Die Meldung nennt die Lösung selbst: git push --set-upstream origin feat/x (oder kurz git push -u origin feat/x). Danach genügt git push.
! [rejected] main -> main (fetch first) Jemand anderes hat seit deinem letzten Fetch auf denselben Branch gepusht. Integriere zuerst seine Arbeit mit git pull, dann pushe. Repariere es nicht mit git push --force — damit würde die Arbeit der anderen Person gelöscht.
warning: in the working copy of 'main.py', LF will be replaced by CRLF the next time Git touches it Unter Windows ist das eine Warnung, kein Fehler. Windows sowie macOS und Linux beenden Zeilen mit unterschiedlichen Zeichen, und Git sagt dir, dass es das angleichen wird. Der Commit ist trotzdem gut durchgegangen.
.env wurde versehentlich committet Führe git rm --cached .env aus — das behält die Datei auf der Festplatte und entfernt sie nur aus Gits Verfolgung (die Ausgabe ist rm '.env') — dann trage .env in .gitignore ein und committe. Denk aber daran: Der Schlüssel steckt weiterhin im früheren Commit. Wurde er gepusht, gibt es keine sichere Option außer, den Schlüssel zu widerrufen und einen neuen anzulegen.
Schritt 4 von 6 — Vorhersagen
Prüfe dein Verständnis
Drei Nachrichten werden gegen das SUBJECT-Muster des Kapitels geprüft. Was wird ausgegeben?
import re
SUBJECT = re.compile(r"^(?P<type>[a-z]+)(\([a-z0-9-]+\))?: (?P<summary>.+)$")
for subject in ["fix: handle empty input", "Fix: handle empty input", "fix:handle empty input"]:
print(SUBJECT.match(subject) is not None)- A`True`, `False`, `True`
- B`True`, `True`, `True`
- C`True`, `True`, `False`
- D`True`, `False`, `False`
Jemand hat diese Funktion in check_commits.py „repariert“. Jetzt ist die CI grün, selbst bei einem Branch mit wip- und feature: add tests-Commits, und die Ausgabe ist 0 of 0 commit messages need work. Wo steckt der Fehler?
import subprocess
def branch_subjects(base: str) -> list[str]:
result = subprocess.run(
["git", "log", "--reverse", "--format=%s", f"HEAD..{base}"],
capture_output=True,
encoding="utf-8",
)
if result.returncode != 0:
raise SystemExit(f"git log failed: {result.stderr.strip()}")
return result.stdout.splitlines()- ADer Bereich ist umgekehrt: `HEAD..main` bedeutet „in `main`, aber nicht auf diesem Branch“, also kommt keiner der eigenen Commits des Branches zurück; es müsste `main..HEAD` sein
- B`--reverse` verwirft Commits; wenn man es entfernt, werden sie angezeigt
- CDie `returncode`-Prüfung verwirft jeden Commit; sie braucht `check=True`
- DMit `encoding="utf-8"` gibt `git` keine Ausgabe; es braucht `text=True`
Eine PR-Beschreibung enthält Closes #12. Was bewirkt das?
- AWenn der PR in den Standard-Branch gemergt wird, schließt die Code-Plattform Issue #12 von selbst und behält die Verknüpfung der beiden bei
- BIssue #12 wird geschlossen, sobald der PR geöffnet wird
- CNichts — es ist nur eine Notiz für den Reviewer; keine Maschine liest sie
- DGit löscht Commit Nummer 12 aus der Historie
Spielen ist Teil von Pro
+30 XP warten
Spiele dieses Kapitel mit Pro
Lesen ist kostenlos, und das ganze Kapitel ist offen. Die ersten 5 Kapitel jedes kostenlosen Kurses kannst du kostenlos spielen. Ab Kapitel 6 gehören Fragen beantworten, XP sammeln und deine Serie ausbauen zu Pro.
Spielen mit Pro freischaltenJetzt bist du dran
Lege auf deinem eigenen Rechner ein Projekt an und durchlaufe einmal den ganzen Zyklus:
- Lege das Projekt mit
uv init my-triage --python 3.12an, trage.envin.gitignoreein, behalte eine Datei.env.example(die nurLLM_API_KEY=enthält), prüfe, dassgit status --shortweder.envnoch.venvzeigt, und mach den ersten Commit - Füge ein Bare-Repository als Remote hinzu (
git init --bare ../my-triage-remote.git) und pushemain - Lege einen Branch namens
feat/priority-scorean und fügetriage.pyundcheck_commits.pyaus diesem Kapitel in genau drei Commits hinzu, die jeweils den Regeln folgen; pushe den Branch - Schreib eine
PR.md— Was, Warum, Wie getestet — und, falls du ein Konto bei einer Code-Plattform hast, öffne damit einen echten PR dort
Auf dem Branch sollte uv run check_commits.py genau mit dieser Zeile enden, davor drei ok-Zeilen:
0 of 3 commit messages need workDann bau absichtlich einen schlechten Commit ein (git commit --allow-empty -m "wip"), führ das Skript erneut aus, um zu sehen, welche zwei Probleme erkannt werden, und behebe ihn mit git commit --amend -m "...". Oder probier das aus: Ersetze main..HEAD im Skript durch HEAD..main, schau, was es ausgibt, und finde heraus, warum das die gefährlichste Art von Fehler bei einer CI-Prüfung ist.
Schritt 6 von 6
Herausforderung — das Kapitel-Quiz
Zehn Fragen von leicht bis schwer. Die letzten sind absichtlich knifflig.
Mit Pro spielen