Remotes
Bis jetzt haben wir uns sehr viel mit lokaler Version History beschäftigt und du hast nahezu alles gelernt was man braucht um git zu meistern. Bis jetzt haben wir aber gits Konzept zur Kollaboration mit anderen noch nicht angesprochen. Das ist der Punkt an dem Remotes ins Spiel kommen.
Wir und der Server
Das klassische Client-Modell sollte jedem sein Begriff sein. Mehrere Clients greifen auf einen Server zu. Der Server ist der zentrale Punkt an dem alle Clients ihre Daten ablegen und abrufen. Git verfolgt hier einen ähnlichen Ansatz. Der Server ist in diesem Fall ein Remote Repository und die Clients sind unsere lokalen Repositories. Aber wie können wir diese beiden miteinander kombinieren?
Remote Branches
Branches kennen wir ja bereits sehr gut. Mit ihren können wir unsere Arbeit in verschiedene Richtungen aufteilen und später wieder zusammenführen. Grade das Zusammenführen ist für uns jetzt wichtig. Wir wollen unsere Arbeit mit der Arbeit anderer zusammenführen. Klingt vertraut? Das ist genau das was wir mit Merging und Rebasing gemacht haben. Der Server ist für uns also nichts weiter als weitere Branches auf die wir unsere Arbeit zusammenführen können.
Anders als unsere lokalen Branches arbeiten wir auf unseren Remote Branches nicht mit git commit
sondern mit git push
und git pull
. Aber lass uns das erstmal Konzeptuell verstehen.
Am Anfang haben wir ein remote Repository (z.B. auf Github). Wir clone dieses und schaffen damit automatisch eine Verknüfung zwischen remote und local repository. Diese Verknüpfung wird standartmäßig als origin
bezeichnet, kann aber einen beliebigen Namen haben. Mit git remote
können wir uns alle Remote Verknüfungen anzeigen lassen.
$ git remote
> origin
Bei unserem Repsository gibt es eine Verknüpfung names origin
.
Sollten wir nicht mehr wissen wo ein Remote hinzeigt, könnten wir das und noch mehr mit
git remote show <remote_verknüfung>
anzeigen lassen. Fürorigin
wäre dasgit remote show origin
.
Zusätzlich hat unser Repository zwei Branches: main
und origin/main
. Der Branch main
ist unser lokaler Branch und origin/main
ist ein Remote Branch, der den Stand des Branches main
auf dem Server wiederspiegelt.
Nach einem git clone
sind beide Branches auf dem gleichen Stand.
Client:
gitGraph commit id: " " branch origin/main commit id: "A " commit id: "B " commit id: "C " checkout main merge origin/main
Server:
%%{init: { 'gitGraph': { 'mainBranchName': 'origin/main' }} }%% gitGraph commit id: "A" commit id: "B" commit id: "C"
Soweit so einfach. Da Server und Client nicht immer auf dem gleichen Stand sind, kann es passieren, dass beide schnell auseinander driften. Wenn der Server einen neuen Commit bekommt wird unser Remote Branch origin/main
nicht automatisch aktualisiert. Das müssen wir manuell mit git fetch
machen.
Wenn der Server einen neuen Commit bekommt und wir git fetch
ausführen sieht danach unser Repository so aus:
$ git fetch origin main:main
origin main:main
ist ein bischen viel auf einmal.origin
ist der Name der Verknüpfung auf die wir uns beziehen. Das erstemain
ist der Name des Branches auf dem Server den wir fetchen wollen und das zweitemain
ist der Name des Branches in unserem lokalen Repository in dem wir den Branch speichern wollen.
Client:
gitGraph commit id: " " branch origin/main commit id: "A " commit id: "B " commit id: "C " checkout main merge origin/main checkout origin/main commit id: "D"
Server:
%%{init: { 'gitGraph': { 'mainBranchName': 'origin/main' }} }%% gitGraph commit id: "A" commit id: "B" commit id: "C" commit id: "D"
Auf main selber sind die Änderungen noch nicht angekommen. Das müssen wir manuell machen. Dafür gibt es zwei Möglichkeiten: Das klassische git merge
oder das etwas komplexere git rebase
. Beide Methoden kennst du bereits.
Wir entscheiden uns hier für das einfache git merge
$ git checkout main
$ git merge origin/main
Client:
gitGraph commit id: " " branch origin/main commit id: "A " commit id: "B " commit id: "C " checkout main merge origin/main checkout origin/main commit id: "D" checkout main merge origin/main
Server:
%%{init: { 'gitGraph': { 'mainBranchName': 'origin/main' }} }%% gitGraph commit id: "A" commit id: "B" commit id: "C" commit id: "D"
Da dieser Vorgang sehr häufig vorkommt git bietet git eine Abkürzung an: git pull
. git pull
führt git fetch
und git merge
in einem Schritt aus.
$ git pull origin main:main
So kommen wir deutlich simpler zum gleichen Ergebnis.
Sollten wir bereits einen Upstream für diesen Branch gesetzt haben, können wir auch einfach
git pull
ohneorigin main
ausführen. Zur Upstream später mehr
Nun zum Übertragen von Commits auf den Server. Erstellen wir dafür zunächst einen neuen Commit auf unserem Client.
$ git commit
Client:
gitGraph commit id: " " branch origin/main commit id: "A " commit id: "B " commit id: "C " checkout main merge origin/main checkout origin/main commit id: "D" checkout main merge origin/main commit id: "E"
Server:
%%{init: { 'gitGraph': { 'mainBranchName': 'origin/main' }} }%% gitGraph commit id: "A" commit id: "B" commit id: "C" commit id: "D"
Wollen wir diesen Commit auf den Server übertragen, müssen wir zunächst sicherstellen, dass wir auf dem neusten Stand vom Server sind. Sollte das nicht der Fall sein, müssen wir erst einen git pull
durchführen. Damit wird sichergestellt, dass wir beim Pushen keine Konflikte verursachen und ein Fast-Forward-Merge auf dem Server durchgeführt werden kann.
$ git pull origin main:main
> Already up to date.
$ git push origin main:main
git push origin main:main
ist ein wenig anders als beigit fetch
odergit pull
. Zwar istorigin
wieder der Name der Verknüpfung, aber das erstemain
steht hier für den lokalen Branch und das zweitemain
steht für den Branch auf dem Server. Von links nach rechts folgt es also der Reihenfolge der Operation. Fürpush
: lokal -> remote fürfetch
undpull
: remote -> lokal
Der Push-Vorgang setzt zunächst den Remote Branch auf den Stand unseres lokalen Branches und übertragt dann seine Änderungen auf den Server.
Client:
gitGraph commit id: " " branch origin/main commit id: "A " commit id: "B " commit id: "C " checkout main merge origin/main checkout origin/main commit id: "D" checkout main merge origin/main commit id: "E" checkout origin/main merge main
Server:
%%{init: { 'gitGraph': { 'mainBranchName': 'origin/main' }} }%% gitGraph commit id: "A" commit id: "B" commit id: "C" commit id: "D" commit id: "E"
Upstreams
Das ganze Prozedere mit origin main:main
ist ein wenig umständlich. Deshalb lässt sich dies auch einmalig für einen Branch festlegen und danach einfach ein git pull
bzw. git push
ausführen. Wir checken dafür zunächst den gewünschten Branch aus und setzen den Upstream mit git branch --set-upstream-to=<remote>/branch
.
Für den Branch main
auf dem Server sieht das so aus:
$ git checkout main
$ git branch --set-upstream-to=origin/main
# oder kürzer
$ git checkout main
$ git branch -u origin/main
Bei einem Clone wird der Upstream automatisch für den Branch gesetzt. Das heißt für ein geclontes Repository ist
git pull
undgit push
ohne weiteres fürmain
möglich.
Tipp
Wenn ihr in eurem Repository einen neuen Branch erstellt habt und diesen auf den Server pushen wollt, könnt ihr das mit dem bereits bekannten git push origin <branchname>:<branchname>
machen. Danach müsst ihr nur noch den Upstream setzten und könnt mit git pull
und git push
arbeiten.
Identisch aber deutlich komfortabler ist der Befehlt git push -u origin <branchname>
. Dieser erstellt den Branch auf dem Server und setzt den Upstream in einem Schritt.