Skip to the content.

Εισαγωγή στο Git

Σας έχει τύχει να δημιουργήσετε εκδόσεις αρχείων ή φακέλων για κάτι στο οποίο δουλεύετε, π.χ. MyProject_v1, MyProject_v2 ή Document_v1.txt, Document_v2.txt, κλπ.; Πόσο εύκολο ήταν; Ανάλογα με το μέγεθος του έργου σας, ξοδεύετε χώρο στο δίσκο αντιγράφοντας όλα τα αρχεία ενώ μεταξύ δύο εκδόσεων μπορεί να έχετε τροποποιήσει μόνο λίγα αρχεία. Επίσης, πόσο εύκολο είναι να εργαστείτε με άλλους στην ίδια έκδοση του έργου σας; Εδώ είναι που τα Συστήματα Διαχείρισης Εκδόσεων (ΣΔΕ) ή version control systems (VCS) έρχονται να κάνουν τη ζωή μας πιο εύκολη.

Σε αυτό το εισαγωγικό άρθρο θα μάθουμε για την ιστορία των ΣΔΕ, τις διάφορες κατηγορίες τους και θα επικεντρωθούμε στις βασικές εντολές του πιο διαδεδομένου ΣΔΕ σήμερα, το Git. Αφού μάθετε τις βασικές εντολές του Git, τότε μπορείτε να συνεχίσετε με το επόμενο άρθρο Git για προχωρημένους για να εντρυφήσετε στις δυνατότητές του.

Ιστορία

Υπάρχουν τρεις κατηγορίες ΣΔΕ: τοπικά (local), κεντροποιημένα (centralized) και κατανεμμημένα (distributed).

Τα τοπικά ΣΔΕ δεν επέτρεπαν συνεργασία με άλλους χρήστες, κάθε χρήστης είχε δηλ. το δικό του αποθετήριο (repository) τοπικά όπου μπορούσε μόνο να διαχειρίζεται εκδόσεις των αρχείων του. Τα πιο γνωστά τοπικά ΣΔΕ ήταν:

Τα κεντροποιημένα ΣΔΕ ακολουθούν το μοντέλο πελάτη-διακομιστή (client-server) όπου υπάρχει ένας κεντρικός διακομιστής (server) στον οποίο έχει εγκατασταθεί το server πρόγραμμα του ΣΔΕ και σ’ αυτόν έχει δημιουργηθεί το κεντρικό αποθετήριο (repository). Οι διάφοροι χρήστες συνδέονται στον διακομιστή, και κάνουν αλλαγές στο κεντρικό αποθετήριο. Αν ο διακομιστής βγει εκτός λειτουργίας, τότε η διαδικασία αποτυγχάνει. Τα πιο γνωστά κεντροποιημένα ΣΔΕ είναι:

Στην τελευταία κατηγορία ανήκουν ΣΔΕ όπως το Mercurial και το Git, παιδί του Linux, το τελευταίο, που δημιουργήθηκε για την ανάπτυξη του πυρήνα του Linux από τους ανά τον κόσμο προγραμματιστές. Αυτά τα συστήματα δεν περιορίζονται σ’ έναν διακομιστή, ο οποίος μπορεί να εξυπηρετεί μόνο μέχρι έναν ορισμένο αριθμό από χρήστες, ενώ όταν αυτός ‘κρασάρει’, τότε σταματούν και οι ενημερώσεις των εκδόσεων. Αντίθετα, στα κατανεμημένα συστήματα εκδόσεων, κάθε χρήστης έχει το δικό του τοπικό αποθετήριο (repository) των εκδόσεων, κι όταν χρειαστεί να συνεργαστεί με άλλους χρήστες, τότε γίνεται συγχώνευση των αποθετηρίων τους. Με άλλα λόγια, κάθε χρήστης έχει το δικό του backup και δεν απαιτείται συνεχής σύνδεση στο δίκτυο για να υποβληθούν οι αλλαγές (commit). Επιπλέον, το Git έπρεπε να είναι αξιόπιστο, γρήγορο και να μπορεί να διαχειρίζεται μεγάλα έργα (όπως π.χ. τον κώδικα του Linux). Τα πιο γνωστά κατανεμημένα ΣΔΕ είναι:

Οι λόγοι χρήσης συστημάτων διαχείρισης εκδόσεων είναι πολλοί:

Με λίγα λόγια τα βασικά χαρακτηριστικά ενός ΣΔΕ είναι:

Παράλληλα, τo Git προσφέρει πολλά πλεονεκτήματα συγκριτικά με άλλα συστήματα διαχείρισης εκδόσεων:

Το Git είναι πιο πολύπλοκο από το Mercurial, όπως θα δούμε στη συνέχεια, αλλά είναι το πιο δημοφιλές σύστημα διαχείρισης εκδόσεων σήμερα.

Εγκατάσταση

Η εγκατάσταση του Git στο Ubuntu ακολουθεί την πεπατημένη:

$ sudo apt-get install git

Εργαλεία

Πέραν από τις εντολές της γραμμής εντολών που θα μάθουμε στη συνέχεια, υπάρχουν και πολλά γραφικά εργαλεία ανάλογα με το λειτουργικό σας σύστημα, με μερικά από τα πιο γνωστά:

Βασικές εντολές

Ας δούμε τι μπορούμε να κάνουμε με το Git:

$ git version
git version 2.23.0

Η δικιά σας έκδοση μπορεί να διαφέρει.

$ git        
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

These are common Git commands used in various situations:

start a working area (see also: git help tutorial)
   clone     Clone a repository into a new directory
   init      Create an empty Git repository or reinitialize an existing one

work on the current change (see also: git help everyday)
   add       Add file contents to the index
   mv        Move or rename a file, a directory, or a symlink
   restore   Restore working tree files
   rm        Remove files from the working tree and from the index

examine the history and state (see also: git help revisions)
   bisect    Use binary search to find the commit that introduced a bug
   diff      Show changes between commits, commit and working tree, etc
   grep      Print lines matching a pattern
   log       Show commit logs
   show      Show various types of objects
   status    Show the working tree status

grow, mark and tweak your common history
   branch    List, create, or delete branches
   commit    Record changes to the repository
   merge     Join two or more development histories together
   rebase    Reapply commits on top of another base tip
   reset     Reset current HEAD to the specified state
   switch    Switch branches
   tag       Create, list, delete or verify a tag object signed with GPG

collaborate (see also: git help workflows)
   fetch     Download objects and refs from another repository
   pull      Fetch from and integrate with another repository or a local branch
   push      Update remote refs along with associated objects

'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.
See 'git help git' for an overview of the system.

$ git help
usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

Οι παρακάτω εντολές παρέχουν λεπτομερή βοήθεια για τις εντολές του Git:

$ git help
$ git help <εντολή>
$ man git
$ man gitignore

Ρυθμίζοντας το Git

Προτού όμως μπορέσουμε να υποβάλλουμε τις αλλαγές μας σε ένα αποθετήριο, θα πρέπει το Git να γνωρίζει το email μας και ένα όνομα χρήστη, π.χ. (δώστε τα δικά σας):

$ git config --global user.name "hawk"
$ git config --global user.email "nobody@nowhere.gr"

Οι πληροφορίες αυτές αποθηκεύονται στο αρχείο ~/.gitconfig το οποίο επηρεάζει όλους τους χρήστες του μηχανήματος στο οποίο δουλεύετε:

$ vi ~/.gitconfig
[user]
        name = hawk
        email = nobody@nowhere.gr

Σημείωση. Πέραν από τις καθολικές ρυθμίσεις, μπορείτε να κάνετε και ρυθμίσεις ανά αποθετήριο. Κάθε αποθετήριο βρίσκεται σ’ έναν κρυφό υποφάκελο .git μέσα στον οποίο υπάρχει ένας υποφάκελος config.

Άλλες ρυθμίσεις που θα μπορούσατε να ορίσετε είναι π.χ. ο επεξεργαστής κειμένου. Εξ’ ορισμού το git χρησιμοποιεί τον vi. Αν δε σας αρέσει ο vi μπορείτε να χρησιμοποιήσετε π.χ. τον nano ή τον emacs ή τον gedit ή το TextMate ή το Visual Studio Code (ή στα windows π.χ. το notepad++):

$ git config --global core.editor gedit

Άλλες ρυθμίσεις:

$ git config --global color.ui true
$ git config --global merge.tool opendiff

Για να δείτε όλες τις ρυθμίσεις git του συστήματός σας δώστε την εντολή:

$ git config –-list --global 

Δουλεύοντας με αποθετήρια git

Υπάρχουν 2 τρόποι για να δουλέψετε με ένα αποθετήριο git:

Δημιουργία αποθετηρίου

Για να δημιουργήσετε ένα νέο αποθετήριο myproject, χρησιμοποιείστε την εντολή init στο φάκελο που θέλετε να δημιουργήσετε το αποθετήριό σας:

$ mkdir -p ~hawk/projects/
$ cd ~hawk/projects
$ git init myproject
Initialized empty Git repository in ~hawk/projects/myproject/.git/
$ cd myproject
$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

Η εντολή status εμφανίζει σε τι κατάσταση βρίσκεται το αποθετήριό σας, π.χ. αν υπάρχουν αλλαγές σε υπάρχοντα αρχεία, νέα αρχεία ή αρχεία που διαγράφηκαν κλπ.

Στη συνέχεια, μπορείτε να αντιγράψετε ή να δημιουργήσετε κάποια αρχεία στο νέο αποθετήριο, και να ενημερώσετε το Git για τις ενέργειές σας, δίνοντας π.χ.:

$ touch hello.c
$ touch goodbye.c
$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	goodbye.c
	hello.c

nothing added to commit but untracked files present (use "git add" to track)

Η εντολή status εμφανίζει την κατάσταση του αποθετηρίου σας. Παραπάνω βλέπουμε ότι υπάρχουν δυο νέα αρχεία C τα οποία δεν έχουν υποβληθεί στο τοπικό αποθετήριο (untracked). Εμφανίζει επίσης τον κλάδο (branch) στο οποίο βρισκόμαστε και ο οποίος ονομάζεται master. Θα μιλήσουμε για κλάδους παρακάτω.

Σημείωση. Σ΄αυτό το άρθρο θα χρησιμοποιήσουμε ως παραδείγματα αρχεία .c αλλά το git δουλεύει με οποιονδήποτε τύπο αρχείων κειμένου, π.χ. .txt, .md, .java κλπ. Αποφύγετε να αποθηκεύετε δυαδικά αρχεία (π.χ. docx, .exe, .png) στο αποθετήριο του git.

Σημείωση. Αν γνωρίζετε το mercurial τότε οι αντίστοιχες εντολές στο mercurial είναι:

$ hg init
$ hg status

Υποβολή των αλλαγών

Η διαδικασία υποβολής αλλαγών στο git αποτελείται από 2 στάδια:

  1. Προσθήκη των αλλαγών στην πρόχειρη εξέδρα (stage). Αυτό μας επιτρέπει από τα αλλαγμένα αρχεία να επιλέξουμε μόνο εκείνα που θέλουμε να υποβάλλουμε (commit).
$ git add <files>

ή για να προσθέσετε για υποβολή και αρχεία που διαγράψατε:

$ git add –A <files>

ή αν θέλετε να προσθέσετε όλα τα αρχεία για υποβολή:

$ git add --all

ή

$ git add .

ή αν θέλετε να προσθέσετε για υποβολή μόνο συγκεκριμένα αρχεία μπορείτε να χρησιμοποιήσετε και χαρακτήρες μπαλαντέρ:

$ git add *.c

Για να επιβεβαιώσετε ότι όντως υπάρχουν αρχεία στην περιοχή stage, δώστε:

$ git status

ή

$ git status -s

όπου -s σημαίνει short.

Έτσι, για να προσθέσουμε στην πρόχειρη εξέδρα τις αλλαγές του myproject:

$ git add .
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   goodbye.c
	new file:   hello.c

Με την εντολή add . προσθέσαμε όλα τα νέα αρχεία του τρέχοντος καταλόγου στην περιοχή στάσης ή staging area.

Σημείωση. Στο Mercurial δεν υπάρχει αυτό το βήμα, δηλ. το stage.

  1. Υποβολή των αλλαγών (commit)

Όταν έχετε κατασταλάξει για το ποιές αλλαγές θα υποβάλλετε στο αποθετήριό σας, μπορείτε να τις υποβάλλετε με την εντολή commit:

$ git commit –m "message"

Σημείωση. Η αντίστοιχη εντολή στο mercurial είναι:

$ hg commit –m "message"

Αφού λοιπόν προσθέσαμε όλα τα αρχεία του myproject στην πρόχειρη εξέδρα και είμαστε ικανοποιημένοι με τις αλλαγές, καιρός να τα υποβάλλουμε στο αποθετήριό μας:

$ git commit -m 'Initial commit'
[master (root-commit) 488273f] Initial commit
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 goodbye.c
 create mode 100644 hello.c
 
$ git status
On branch master
nothing to commit, working tree clean

$ git log
git log
commit 488273f6a5ab8162629e105c7c175094819151ed (HEAD -> master)
Author: hawk <nobody@nowhere.gr>
Date:   Sun Jul 7 13:56:51 2024 +0200

    Initial commit
    
$ git log --pretty=oneline
488273f6a5ab8162629e105c7c175094819151ed Initial commit

Με την εντολή commit καταχωρούμε πλέον τις αλλαγές μας στο τοπικό αποθετήριο (.git). Η εντολή log μας δείχνει το ιστορικό. Ακολουθούμε την ίδια σειρά για κάθε αλλαγή που κάνουμε στα αρχεία μας, δηλ. git add, git commit -m "Message".

Σημείωση. Η αντίστοιχη εντολή του mercurial είναι:

$ hg log

Αλλά τι ακριβώς συμβαίνει εσωτερικά στο git με κάθε υποβολή; Παρατηρήστε τη γραμμή:

commit 488273f6a5ab8162629e105c7c175094819151ed (HEAD -> master)

Ο δεκαεξαδικός αριθμός 40 ψηφίων 488273f6a5ab8162629e105c7c175094819151ed είναι ένα SHA-1 hash που είναι μοναδικό για κάθε υποβολή. Με κάθε υποβολή (commit), το git δημιουργεί ένα νέο στιγμιότυπο (snapshot) του αποθετηρίου που μπορεί να ταυτοποιηθεί με αυτό το hash.

Ο HEAD είναι ένας δείκτης που δείχνει κάθε φορά στην τελευταία υποβολή στον κλάδο που βρισκόμαστε.

Ας αλλάξουμε τα περιεχόμενα του αρχείου hello.c:

#include <stdio.h>

int main(void) {
    puts("Hello, World");
    return 0;
}
$ git diff hello.c
diff --git a/hello.c b/hello.c
index e69de29..6001b67 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void) {
+    puts("Hello, World");
+    return 0;
+}

Η εντολή diff μας επιτρέπει να δούμε τις αλλαγές μας σε σχέση με την staging area. Παραπάνω, το a αναφέρεται στο αρχείο hello.c που βρίσκεται στην περιοχή στάσης και b στο αρχείο hello.c που μόλις αλλάξαμε. Όπως βλέπουμε, στην περιοχή στάσης το hello.c είναι κενό, ενώ τα + δηλώνουν τις γραμμές που προστέθηκαν στο αρχείο.

$ git add hello.c
$ git diff hello.c
$ git diff --staged        
diff --git a/hello.c b/hello.c
index e69de29..6001b67 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void) {
+    puts("Hello, World");
+    return 0;
+}

Παρατηρούμε ότι όταν προσθέσουμε το αρχείο μας στην staging area, θα πρέπει να δώσουμε την εντολή git diff --staged η οποία τώρα συγκρίνει το αρχείο hello.c στο αποθετήριο .git (a) με το αρχείο hello.c στην περιοχή στάσης (b).

Γενικά η εντολή diff συγκρίνει δυο αρχεία ή ακόμα και φακέλους ή ακόμα και υποβολές (commits), ανάλογα τι θα της περάσετε ως παραμέτρους.

Μπορούμε φυσικά να αλλάξουμε το αρχείο ενώ αυτό είναι ήδη στην περιοχή στάσης.

#include <stdio.h>

int main(void) {
    puts("Γειά σου κόσμε!");
    return 0;
}
$ git diff
diff --git a/hello.c b/hello.c
index 6001b67..b61ef6d 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
 int main(void) {
-    puts("Hello, World");
+    puts("Γειά σου κόσμε!");
     return 0;
 }

Η παραπάνω εντολή μας δείχνει τι άλλαξε στο αρχείο hello.c μεταξύ της περιοχής στάσης και του φακέλου μας.

$ git diff --staged
diff --git a/hello.c b/hello.c
index e69de29..6001b67 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void) {
+    puts("Hello, World");
+    return 0;
+}

Η παραπάνω εντολή μας δείχνει τι άλλαξε στο αρχείο hello.c μεταξύ του αποθετηρίου μας (.git) και της πρόχειρης περιοχής (staging area).

$ git add .
$ git diff --staged
diff --git a/hello.c b/hello.c
index e69de29..b61ef6d 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void) {
+    puts("Γειά σου κόσμε!");
+    return 0;
+}
$ git commit -m "Updated hello.c"
git commit -m 'Updated hello.c'
[master 245fa8f] Updated hello.c
 1 file changed, 6 insertions(+)

$ git log --pretty=oneline
245fa8fc3dfa2c8c308a07bdcb9b152329006d84 (HEAD -> master) Updated hello.c
488273f6a5ab8162629e105c7c175094819151ed Initial commit

$ git log --graph --oneline --all
* 245fa8f (HEAD -> master) Updated hello.c
* 488273f Initial commit

Η τελευταία εντολή μας επιτρέπει να δούμε τις αλλαγές σε γραφική (δενδρική μορφή). Αν εγκαταστήσατε κάποιο γραφικό εργαλείο όπως π.χ. το SourceTree ή το GitKraken θα μπορείτε να δείτε το ιστορικό σε γραφική μορφή:

Εικόνα 1 Ιστορικό του αποθετηρίου μας όπως φαίνεται στο SourceTree

$ gcc -o hello.exe hello.c
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	hello.exe

nothing added to commit but untracked files present (use "git add" to track)

.gitignore

Υπάρχουν αρχεία που δε θα θέλατε να προσθέσετε στο αποθετήριό σας. Τέτοια αρχεία είναι π.χ. εκτελέσιμα ή δυαδικά αρχεία, αρχεία που παράγονται από διάφορα εργαλεία, προσωρινά αρχεία κλπ. γενικά αρχεία που μπορείτε εύκολα να παράγετε ή δεν έχει νόημα να υποβάλλετε στο αποθετήριό σας. Για να μην τα προσθέσετε κατά λάθος στο αποθετήριο, μπορείτε να προσθέσετε τα ονόματά τους στο αρχείο .gitignore που μπορείτε να δημιουργήσετε στον αρχικό κατάλογο του έργου σας και στο οποίο μπορείτε να προσθέσετε π.χ. περιεχόμενα όπως τα παρακάτω:

# Ignore all *.txt files
*.txt

# do not ignore readme.txt
!readme.txt

# Ignore .md files only in the root folder
/*.md

#Ignore all .java files in folders and subfolders of gen folder
gen/**/.java

# Ignore java binary files
*.class
*.jar

# Ignore C/C++ binary files
*.exe
*.[oa]
a.out

#Ignore folders
tmp/
bin/
lib/

Οι γραμμές που ξεκινούν με τη δίεση (#) είναι σχόλια.

$ ls -a
.		..		.git		.gitignore	goodbye.c	hello.exe		hello.c
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.gitignore

nothing added to commit but untracked files present (use "git add" to track)
$ git add .
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   .gitignore
$ git commit -m "Added .gitignore"	
[master bd054a7] Added .gitignore
 1 file changed, 26 insertions(+)
 create mode 100644 .gitignore

Μπορείτε να βρείτε έτοιμα αρχεία .gitignore για διάφορες γλώσσες προγραμματισμού εδώ.

Κλωνοποίηση αποθετηρίου

Για να μπορέσουν να δουλέψουν κι άλλοι πάνω σ’ ένα αποθετήριο θα πρέπει να ‘ναι προσβάσιμο από κάποια κοινή (και ασφαλή) τοποθεσία. Θα δημιουργήσουμε ένα κεντρικό αποθετήριο όπου θα μπορούν οι προγραμματιστές να συγχρονίζουν τις αλλαγές τους.

$ cd ..
$ cp -R myproject /projects/

όπου υποθέστε ότι ο φάκελος /projects βρίσκεται σε έναν κοινόχρηστο φάκελο. Θα μπορούσε να είναι ένα αποθετήριο στο github ή στο heroku ή στο gitlab ή σε κάποιον φάκελο NFS.

Ένας άλλος προγραμματιστής μπορεί τώρα να κλωνοποιήσει αυτόν τον φάκελο και να συνεχίσει να δουλεύει σ’ αυτό το έργο.

$ cd ~nick/projects
$ git clone /projects/myproject
Cloning into 'myproject'...
done.
$ cd myproject
$ git log --oneline --graph --all
* 245fa8f (HEAD -> master) Updated hello.c
* 488273f Initial commit

Σημείωση. Η αντίστοιχη εντολή του mercurial είναι:

$ hg clone <αποθετήριο>

Αν τώρα ο Νίκος κάνει κάποια αλλαγή στο τοπικό αποθετήριό του, θα πρέπει να ενημερώσει και το απομακρυσμένο αποθετήριο. Θα δούμε πώς αργότερα.

Κλάδοι (branches)

Μερικές φορές θέλετε να δοκιμάσετε νέα πράγματα χωρίς να ρισκάρετε να πειράξετε την τρέχουσα κατάσταση των αρχείων σας. Μπορείτε να δημιουργήσετε ένα νέο κλαδί (branch) για να δοκιμάσετε τις αλλαγές σας, κι αν όλα πάνε καλά να συγχωνεύσετε (merge) τις αλλαγές σας στο κύριο κλάδο.

$ git status
On branch master
nothing to commit, working tree clean

$ git branch
* master

μας λέει ότι είμαστε τον κλάδο mastermain σε άλλα αποθετήρια).

Σημείωση. Μπορείτε να αλλάξετε τον όνομα του κύριου κλάδου από master π.χ. σε main με την εντολή git branch -M main.

Μπορούμε να δημιουργήσουμε ένα νέο κλάδο με την εντολή git branch <όνομα>, π.χ.

$ git branch newbranch
$ git branch          
* master
  newbranch

Για να μεταβούμε στο νέο κλάδο που δημιουργήσαμε:

$ git switch newbranch
Switched to branch 'newbranch'
$ git branch            
  master
* newbranch

Σημείωση! Πριν την έκδοση 2.23.0 η εντολή μετάβασης σε άλλο κλάδο ήταν η git checkout newbranch.

Σημείωση! Οι παραπάνω δυο εντολές

$ git branch newbranch
$ git switch newbranch

μπορούν να συνδυαστούν σε μία

$ git switch -c newbranch

Ας κάνουμε κάποιες αλλαγές σε αυτόν τον κλάδο. Ας αλλάξουμε τα περιεχόμενα του αρχείου goodbye.c:

#include <stdio.h>

int main(void) {
    puts("Αντίο κόσμε!");
    return 0;
}
$ git status
On branch newbranch
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   goodbye.c

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .
$ git commit -m "Updated goodbye.c"
[newbranch df85b3f] Updated goodbye.c
 1 file changed, 7 insertions(+)
$ git log --oneline --graph --all
* df85b3f (HEAD -> newbranch, master) Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git switch master
Switched to branch 'master'
$ more goodbye.c

$ git log --oneline --graph --all
* bd054a7 (HEAD -> master, newbranch) Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Παρατηρούμε ότι οι αλλαγές μας δεν υπάρχουν στο master branch (το αρχείο goodbye.c είναι κενό). Για να μεταφέρουμε τις αλλαγές μας από τον κλάδο newbranch στον master, δίνουμε:

$ git merge newbranch
Updating bd054a7..df85b3f
Fast-forward
 goodbye.c | 7 +++++++
 1 file changed, 7 insertions(+)
$ more goodbye.c     
#include <stdio.h>

int main(void) {
    puts("Αντίο κόσμε!");
    return 0;
}

goodbye.c (END)
$ git log --oneline --graph --all
* df85b3f (HEAD -> master, newbranch) Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Η εντολή merge συγχωνεύει τον κλάδο newbranch στον τρέχοντα κλάδο, δηλ. τον master. Πλέον η αλλαγή df85b3fe814... υπάρχει και στο master branch. Παρατηρήστε ότι αυτή η συγχώνευση είναι fast-forward καθώς δεν υπήρξαν προβλήματα συγχώνευσης. Παρακάτω θα δούμε και μια άλλη περίπτωση συγχώνευσης.

Μπορούμε πλέον να διαγράψουμε τον κλάδο newbranch αν δεν τον χρειαζόμαστε πλέον:

$ git branch -d newbranch
$ git branch
* master

Σημείωση Μπορείτε να χρησιμοποιήσετε μια σύμβαση για την ονομασία των κλάδων (την οποία δεν χρησιμοποιούμε σ’ αυτό το άρθρο) που θα μπορούσε να σας φανεί χρήσιμη. Π.χ. για νέα χαρακτηριστικά της εφαρμογής χρησιμοποιείτε feature/, για διόρθωση σφαλμάτων bug/ κλπ. Έτσι αν δουλεύετε π.χ. στο χαρακτηριστικό δημιουργία νέα παραγγελίας θα μπορούσατε να ονομάσετε τον αντίστοιχο κλάδο ως feature/neworder. Με αυτόν τον τρόπο μπορείτε να ξεχωρίζετε πολύ εύκολα ποιοί κλάδοι είναι για νέα χαρακτηριστικά, ποιοί είναι για διόρθωση σφαλμάτων κλπ. Άλλη σύμβαση είναι να ονομάζετε τον κλάδο σας προσθέτοντας το JIRA id αν χρησιμοποιείτε JIRA (ή κάποιο άλλο σύστημα διαχείρισης σφαλμάτων/χαρακτηριστικών).

Προβλήματα Συγχώνευσης

Ας υποθέσουμε ότι δουλεύετε σε ένα νέο χαρακτηριστικό σε έναν κλάδο (feature1branch) κι ένας άλλος συνάδελφος προσπαθεί να διορθώσει ένα σφάλμα και δουλεύει σε έναν άλλο κλάδο (bug1branch):

$ git branch feature1branch
$ git branch bug1branch
$ git branch
  bug1branch
  feature1branch
* master

Εσείς αλλάζετε το αρχείο hello.c αλλάζοντας το μήνυμα που εμφανίζεται στο χρήστη:

$ git switch feature1branch
Switched to branch 'feature1branch'
$ vi hello.c
#include <stdio.h>

int main(void) {
    puts("Γειά χαρά σου κόσμε!");
    return 0;
}
$ git diff
diff --git a/hello.c b/hello.c
index b61ef6d..03cae4e 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
 int main(void) {
-    puts("Γειά σου κόσμε!");
+    puts("Γειά χαρά σου κόσμε!");
     return 0;
 }
$ git add .
$ git commit -m "Modified hello.c"
[feature1branch 2946b52] Modified hello.c
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --all
* | 2946b52 (HEAD -> feature1branch, master) Modified hello.c
|/ 
* df85b3f (master, bug1branch) Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git switch master
Switched to branch 'master'
$ git log --oneline --graph --all
* df85b3f (HEAD -> master, bug1branch) Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git merge feature1branch
Updating df85b3f..2946b52
Fast-forward
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --all
* | 2946b52 (HEAD -> master, feature1branch) Modified hello.c
|/ 
* df85b3f (bug1branch) Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Ο άλλος συνάδελφος που δουλεύει στον κλάδο bug1branch τροποποιεί το ίδιο αρχείο:

$ git switch bug1branch
Switched to branch 'bug1branch'
vi hello.c
#include <stdio.h>

int main(void) {
    puts("Γειά σου git!");
    return 0;
}
$ git diff
diff --git a/hello.c b/hello.c
index b61ef6d..e99c37a 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
 int main(void) {
-    puts("Γειά σου κόσμε!");
+    puts("Γειά σου git!");
     return 0;
 }
$ git add .
$ git commit -m "Fixed hello.c"
[bug1branch 00937c5] Fixed hello.c
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --all
* | 00937c5 (HEAD -> bug1branch, master) Fixed hello.c
|/ 
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git switch master
Switched to branch 'master'
$ git log --oneline --graph --all
* | 2946b52 (HEAD -> master, feature1branch) Modified hello.c
|/ 
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git merge bug1branch
Auto-merging hello.c
CONFLICT (content): Merge conflict in hello.c
Automatic merge failed; fix conflicts and then commit the result.

Αυτή είναι η δεύτερη περίπτωση συγχώνευσης, όπου υπάρχει σύγκρουση (conflict).

Ας ανοίξουμε το αρχείο hello.c στον αγαπημένο μας επεξεργαστή κειμένου:

$ vi hello.c
#include <stdio.h>

int main(void) {
<<<<<<< HEAD
    puts("Γειά χαρά σου κόσμε!");
=======
    puts("Γειά σου git!");
>>>>>>> bug1branch
    return 0;
}

Στο πάνω μέρος από το ======= βλέπουμε τι περιέχει το master branch ενώ στο κάτω μέρος τις αλλαγές του κλάδου bug1branch. Θα πρέπει να αποφασίσετε ποιές αλλαγές να κρατήσετε και να αποθηκεύσετε το αρχείο με τα σωστά περιεχόμενα.

#include <stdio.h>

int main(void) {
    puts("Γειά σου git!");
    return 0;
}

Στην περίπτωσή μας αποφασίσαμε να κρατήσουμε τα περιεχόμενα του κλάδου bug1branch.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   hello.c

no changes added to commit (use "git add" and/or "git commit -a")

$ git add .

$ git status          
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
        modified:   hello.c
		
$ git commit -m "Merge branch bug1branch"  
[master 285480f] Merge branch bug1branch

$ git branch -d bug1branch
Deleted branch bug1branch (was 00937c5). 

$ git log --oneline --graph --all
*   285480f (HEAD -> master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 (feature1branch) Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Με τον παραπάνω γραφικό τρόπο βλέπουμε ότι δημιουργήσαμε έναν άλλο κλάδο τον οποίο συγχωνεύσαμε στον master branch.

Εικόνα 2 Ιστορικό του αποθετηρίου μας όπως φαίνεται στο SourceTree

Μπορούμε να μεταβούμε σε παλιότερες υποβολές στο ιστορικό με την εντολή checkout. Π.χ. για να μεταβούμε σε μια
προηγούμενη υποβολή δίνουμε:

$ git checkout df85b3f
Previous HEAD position was 00937c5 Fixed hello.c
HEAD is now at df85b3f Updated goodbye.c
$ git log --oneline --all --graph
* 5baedbf (feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f (HEAD) Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Παρατηρήστε ότι ο δείκτης HEAD τώρα δείχνει στην υποβολή df85b3f και τα αρχεία της περιοχής εργασίας έχουν αλλάξει και είναι όπως ήταν στην υποβολή df85b3f. Το αρχείο readme.txt έχει εξαφανιστεί, ενώ τα περιεχόμενα των αρχείων hello.c, goodbye.c δεν περιέχουν τις αλλαγές που έγιναν στις υποβολές 285480f, 5baedbf. Βρίσκεστε πίσω στην ιστορία!

Ένας άλλος τρόπος για να μετακινούμαστε στο ιστορικό είναι σχετικά με τον δείκτη HEAD. Αν επιστρέψουμε στο παρών:

$ git checkout 5baedbf
Previous HEAD position was df85b3f Updated goodbye.c
HEAD is now at 5baedbf Added readme.txt
$ git log --oneline --all --graph
* 5baedbf (HEAD, feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

τότε η προηγούμενη υποβολή 285480f είναι το HEAD~1 κ.ο.κ. Αν υπάρχουν περισσότεροι του ενός πρόγονοι, όπως στην περίπτωση που υπάρχουν κλάδοι, όπως στο παράδειγμά μας, τότε HEAD^1 δηλώνει τον 1ο κλάδο, HEAD^2 τον 2ο κ.ο.κ. Π.χ.

$ git checkout HEAD^1~2
Previous HEAD position was 5baedbf Added readme.txt
HEAD is now at df85b3f Updated goodbye.c

Εξήγηση: HEAD^1 == 285480f δηλ. η προηγούμενη υποβολή στον 1ο κλάδο και HEAD^1~2 == df85b3f δηλ. η προ-προηγούμενη υποβολή στον 1ο κλάδο.

Ας δούμε άλλο ένα παράδειγμα:

$ git checkout 285480f           
Previous HEAD position was df85b3f Updated goodbye.c
HEAD is now at 285480f Merge branch bug1branch
$ git log --oneline --all --graph
* 5baedbf (feature1branch) Added readme.txt
*   285480f (HEAD, master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git checkout HEAD^2          
Previous HEAD position was 285480f Merge branch bug1branch
HEAD is now at 00937c5 Fixed hello.c
$ git log --oneline --all --graph
* 5baedbf (feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 (HEAD) Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git checkout 5baedbf           
Previous HEAD position was 00937c5 Fixed hello.c
HEAD is now at 5baedbf Added readme.txt

Μπορούν να χρησιμοποιηθούν και με την εντολή diff:

$ git diff HEAD~2 HEAD
diff --git a/hello.c b/hello.c
index 03cae4e..e99c37a 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
 int main(void) {
-    puts("Γειά χαρά σου κόσμε!");
+    puts("Γειά σου git!");
     return 0;
 }
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..e69de29

Αναιρέσεις (undos)

“Λάθη είμαστε ανθρώπους κάνουμε” όπως λέει και το ανέκδοτο, οπότε καθώς είναι ανθρώπινο να κάνουμε λάθη θα πρέπει να υπάρχει και τρόπος να τα διορθώνουμε. Και το git διαθέτει πολλές εντολές για κάτι τέτοιο.

Αν δούμε λοιπόν τι μπορούμε να κάνουμε αν μετανιώσαμε με κάποια αλλαγή.

Έστω ότι τροποποιήσαμε το αρχείο goodbye.c όπως παρακάτω:

$ git diff 
diff --git a/goodbye.c b/goodbye.c
index 885c106..a71fe45 100644
--- a/goodbye.c
+++ b/goodbye.c
@@ -1,7 +1,7 @@
 #include <stdio.h>
 
 int main(void) {
-    puts("Αντίο κόσμε!");
+    puts("Αντίο git!")
     return 0;
 }
 
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   goodbye.c

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .

Το git μας λέει ότι μπορούμε να χρησιμοποιήσουμε την εντολή git restore για να αναιρέσουμε τις αλλαγές μας. Η εντολή αυτή είναι η αντίστροφη της git commit.

Παρατηρήστε ότι το αρχείο goodbye.c έχει ένα συντακτικό λάθος στη γραμμή που προσθέσαμε. Κανονικά, θα ‘πρεπε να το μεταγλωττίσουμε και να το τεστάρουμε προτού το υποβάλλουμε στο αποθετήριο.

Ευτυχώς το πήραμε χαμπάρι αρκετά νωρίς, προτού το υποβάλουμε στο αποθετήριο οπότε μπορούμε να αναιρέσουμε τις αλλαγές μας με την εντολή:

$ git restore goodbye.c --staged
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   goodbye.c

no changes added to commit (use "git add" and/or "git commit -a")

η οποία αναιρεί τις αλλαγές μας στην περιοχή στάσης και στη συνέχεια

$ git restore goodbye.c
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

για να αναιρέσουμε τις αλλαγές μας και από τον φάκελο εργασίας μας.

Τι γίνεται όμως αν κατά λάθος υποβάλλαμε το αρχείο στο αποθετήριο; Ας υποθέσουμε ότι υποβάλλαμε κατά λάθος ένα νέο αρχείο.

$ touch readme.txt
$ git add .
$ git commit -m "Added readme.txt"
[feature1branch b22bda1] Added readme.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 readme.txt
$ git log --oneline --all --graph 
* b22bda1 (HEAD -> feature1branch) Added readme.txt
| *   285480f (master) Merge branch bug1branch
| |\  
|/ /  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

τότε μπορούμε να χρησιμοποιήσουμε την εντολή git reset. Υπάρχουν 3 παράμετροι που μπορούμε να περάσουμε σ’ αυτήν την εντολή:

Ας δούμε από ένα παράδειγμα. Για να αναιρέσουμε την τελευταία υποβολή b22bda1, θα πρέπει να δώσουμε στην git reset το hash της προηγούμενης υποβολής, δηλ. 285480f ή HEAD~1:

$ git reset --soft 285480f
$ git log --oneline --all --graph
*   285480f (HEAD -> feature1branch, master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git status
On branch feature1branch
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   hello.c
	new file:   readme.txt
$ git diff --staged
diff --git a/hello.c b/hello.c
index e99c37a..03cae4e 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
 int main(void) {
-    puts("Γειά σου git!");
+    puts("Γειά χαρά σου κόσμε!");
     return 0;
 }
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..e69de29
$ git diff

Βλέπουμε ότι η παράμετρος --soft έκανε ότι είδαμε στη θεωρία, δηλ. αναίρεσε την τελευταία υποβολή b22bda1 από το αποθετήριο, και μετέφερε τις αλλαγές της προηγούμενης υποβολής 285480f τόσο στην πρόχειρη περιοχή όσο και στην περιοχή εργασίας (γι’ αυτό κι η εντολή git diff δεν επιστρέφει τίποτα.)

Ας δοκιμάσουμε τώρα την παράμετρο mixed. Αλλά πριν, ας επιστρέψουμε πάλι εκεί που είμασταν, δηλ. πρέπει πρώτα να αναιρέσουμε τις αλλαγές στο hello.c από την περιοχή στάσης και την περιοχή εργασίας καθώς αυτές είναι ήδη στο αποθετήριο:

$ git restore --staged hello.c
$ git status                  
On branch feature1branch
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   readme.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   hello.c

$ git restore hello.c         
$ git status         
On branch feature1branch
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   readme.txt
$ git add readme.txt
$ git status
On branch feature1branch
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   readme.txt
$ git diff --staged
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..e69de29
$ git commit -m "Added readme.txt"
[feature1branch 45d5a02] Added readme.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 readme.txt
$ git log --oneline --all --graph 
* 45d5a02 (HEAD -> feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git reset 285480f
$ git log --oneline --all --graph
*   285480f (HEAD -> feature1branch, master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Όπως βλέπουμε, η τελευταία υποβολή εξαφανίστηκε από το αποθετήριό μας. Ας δούμε λοιπόν σε τι κατάσταση βρίσκονται η περιοχή στάσης και η περιοχή εργασίας μας:

$ git status
On branch feature1branch
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	readme.txt

nothing added to commit but untracked files present (use "git add" to track)
$ git diff --staged
$ git diff

Όπως βλέπουμε, το αρχείο εμφανίζεται ως νέο αρχείο στην περιοχή εργασίας, ενώ η περιοχή στάσης είναι καθαρή.

Ας δούμε τώρα και την τρίτη επιλογή:

$ git add .
$ git commit -m "Added readme.txt"
[feature1branch a44880c] Added readme.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 readme.txt
$ git log --oneline --all --graph
* a44880c (HEAD -> feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git reset --hard 285480f
HEAD is now at 285480f Merge branch bug1branch
$ git log --oneline --all --graph
*   285480f (HEAD -> feature1branch, master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git status
On branch feature1branch
nothing to commit, working tree clean

Οι αλλαγές μας εξαφανίστηκαν, σαν να μην είχαν συμβεί ποτέ. Η παράμετρος --hard θέλει πολύ προσοχή. Αντί για την git reset --hard ... καλύτερα να δώσετε git reset και αφού είστε σίγουροι git restore.

Ο παρακάτω πίνακας συγκρίνει τις δυο εντολές:

git restore git reset
Δουλεύει στο επίπεδο αρχείων Δουλεύει στο επίπεδο υποβολής
Δεν αλλάζει το ιστορικό (δεν μετακινεί τον δείκτη HEAD) Αλλάζει το ιστορικό (μετακινεί τον δείκτη HEAD)

Η εντολή reset μπορεί να χρησιμοποιηθεί και για να αναιρέσουμε τις αλλαγές μας από την περιοχή στάσης ή πρόχειρη περιοχή, προτού δηλ. τις υποβάλλουμε στο αποθετήριο:

$ touch readme.txt
$ git status
On branch feature1branch
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	readme.txt

nothing added to commit but untracked files present (use "git add" to track)
$ git reset readme.txt
$ git status          
On branch feature1branch
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	readme.txt

nothing added to commit but untracked files present (use "git add" to track)

Με λίγα λόγια, με την εντολή git reset μπορείτε στην ουσία να αναιρέσετε τις αλλαγές σας από παντού.

Τέλος, υπάρχει και η εντολή git reverse <commit_id> η οποία αναιρεί την υποβολή που του δίνουμε αλλά προσθέτει μια νέα υποβολή που δηλώνει ότι η τελευταία υποβολή αναιρέθηκε.

$ git add .
$ git commit -m "Added readme.txt"
$ git log --oneline --all --graph
* 5baedbf (HEAD -> feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit
$ git revert 5baedbf

Παρατηρήστε ότι δεν δίνουμε το hash της προηγούμενης υποβολής αλλά της ίδιας της υποβολής (HEAD) που θέλουμε να αναιρέσουμε. Ανάλογα με τον ποιο επεξεργαστή κειμένου έχετε ορίσει στο git εμφανίζεται το παρακάτω:

Revert "Added readme.txt"

This reverts commit 5baedbfabf8bbc0e94c9384e734a7b65ca5976cd.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch feature1branch
# Changes to be committed:
#       deleted:    readme.txt

Απλά αποθηκεύστε το και θα δείτε:

[feature1branch a0ec9d0] Revert "Added readme.txt"
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 readme.txt
$ git log --oneline --all --graph
* a0ec9d0 (HEAD -> feature1branch) Revert "Added readme.txt"
* 5baedbf Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit 

Παρατηρήστε ότι έχετε πλέον δυο υποβολές (5baedbf και a0ec9d0) στο ιστορικό σας.

Προσωπικά, δεν μου αρέσει να βλέπω κάτι τέτοιο στο αποθετήριό μου, οπότε θα αναιρέσω την τελευταία υποβολή a0ec9d0 αφήνοντας την 5baedbf:

$ git reset --hard 5baedbf
$ git log --oneline --all --graph
* 5baedbf (HEAD -> feature1branch) Added readme.txt
*   285480f (master) Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Σημείωση. Σημειώστε ότι το mercurial δεν έχει εντολές αναίρεσης. Από τη στιγμή που υποβάλλατε κάτι στο αποθετήριο, το παρελθόν δεν μπορεί ν’ αλλάξει.

Απομακρυσμένα αποθετήρια

Η δημιουργία και συντήρηση του κώδικα του Linux αλλά και πολλών άλλων έργων ανοικτού κώδικα γίνεται από προγραμματιστές που βρίσκονται σε κάθε μεριά της γης. Για να συνεργαστούν, χρειάζεται ο κώδικας να βρίσκεται σε ένα ασφαλές αποθετήριο στον παγκόσμιο ιστό όπου θα μπορούν να υποβάλλουν τις αλλαγές τους. Παραδείγματα τέτοιων είναι το github, το bitbucket, το heroku και το gitlab. Στη συνέχεια θα χρησιμοποιήσουμε το github.

Github

Το Github είναι το μεγαλύτερο αποθετήριο πηγαίου κώδικα παγκοσμίως. Μας επιτρέπει να μοιραζόμαστε τα αποθετήριά μας με άλλους και μάλιστα δωρεάν! Προγραμματιστές απ’ όλον τον κόσμο δουλεύουν ατέλειωτες ώρες και συνεισφέρουν σε προγράμματα ανοιχτού κώδικα που είναι αποθηκευμένα στο github (όπως π.χ. ο πυρήνας του Linux, το Apache NetBeans κλπ). Ίσως είναι και ο λόγος που το git επιβλήθηκε ως το πιο δημοφιλές ΣΔΕ.

Δημιουργία Λογαριασμού και Σύνδεση

Αν δεν έχετε ήδη λογαριασμό στο Github, μπορείτε να δημιουργήσετε έναν. Πλοηγηθείτε στον ιστοτόπο https://github.com, πατήστε το κουμπί Sign up και ακολουθήστε τα βήματα. Αφού λύσετε κι ένα κουίζ για να επιβεβαιώσει ότι είστε άνθρωπος, εισάγετε τον κωδικό που λάβατε στο email σας και είστε έτοιμος. Πλέον μπορείτε να συνδεθείτε στο λογαριασμό σας πατώντας στο Sign In.

Όταν συνδεθείτε στο λογαριασμό σας θα σας εμφανιστεί το ταμπλώ (dashboard) με ενέργειες που μπορείτε να κάνετε, όπως να δημιουργήσετε ένα νέο αποθετήριο ή να εισάγετε ένα τοπικό αποθετήριο κλπ. Συνίσταται να εγκαταστήσετε το GitHub Desktop για πιο εύκολη διαχείριση των αποθετηρίων σας στο GitHub.

Εικόνα 3 Το ταμπλώ του Github.

Δημιουργία απομακρυσμένου αποθετηρίου

Θα ακολουθήσουμε τις οδηγίες για να δημιουργήσουμε ένα αποθετήριο στο github και να “ωθήσουμε” (push) το τοπικό μας αποθετήριο myproject στο απομακρυσμένο.

  1. Δημιουργήστε ένα νέο αποθετήριο στο github (είτε πατώντας το πράσινο κουμπί New πάνω αριστερά είτε το + και επιλέγοντας New Repository από το πτυσσόμενο μενού) και δώστε του το ίδιο όνομα με το τοπικό αποθετήριο (δηλ. myproject). Public σημαίνει ότι όλοι μπορούν να το δουν αλλά εσείς αποφασίζετε ποιός(-οί) μπορεί(-ούν) να συνεισφέρει(-ουν) σ’ αυτό. Private σημαίνει ότι εσείς αποφασίζετε ποιός(-οί) μπορεί(-ούν) να το δει(-ουν). Μπορείτε ακόμα να προσθέσετε ένα README.md με πληροφορίες για το έργο σας, .gitignore ακόμα και άδεια χρήσης. Επειδή θέλουμε να ανεβάσουμε το τοπικό μας αποθετήριο στο github, δεν επιλέγουμε τίποτα απ’ αυτά και πατάμε απλώς το κουμπί Create repository…. Το νέο σας αποθετήριο δημιουργήθηκε στη διεύθυνση URL: https://github.com/your-username/your-repository-name, π.χ. https://github.com/jkosta-test/my-project. Μάλιστα το github σας δίνει οδηγίες πώς να καταχωρήσετε το τοπικό σας αποθετήριο στο νέο αποθετήριο.
  2. Στο τοπικό αποθετήριο, ορίστε μια απομακρυσμένη διεύθυνση με την παρακάτω εντολή:
$ git remote add origin  https://github.com/jkosta-test/myproject.git
$ git remote -v
origin	https://github.com/jkosta-test/myproject.git (fetch)
origin	https://github.com/jkosta-test/myproject.git (push)
$  git remote show origin
* remote origin
  Fetch URL: https://github.com/jkosta-test/myproject.git
  Push  URL: https://github.com/jkosta-test/myproject.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

Αντί για το jkosta-test θα πρέπει να δείτε το δικό σας username. Μόλις “συνδέσαμε” το τοπικό μας αποθετήριο με το απομακρυσμένο αποθετήριο του github και μπορούμε να αναφερόμαστε σ’ αυτό με το όνομα origin.

Εδώ θα πρέπει να ξεκαθαρίσουμε κάποια πράγματα:

Ώθηση (push) των αλλαγών

Αν δεν είσαστε ήδη:

$ git switch master
Previous HEAD position was 5baedbf Added readme.txt
Switched to branch 'master'

και στη συνέχεια δώστε την παρακάτω εντολή για να “ωθήσετε” τις αλλαγές σας στο απομακρυσμένο αποθετήριο:

$ git push
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master

Η εντολή git push --set-upstream origin master ή git push -u origin master λέει στο git να αντιστοιχήσει τον τοπικό κλάδο master με τον κλάδο origin/master που αντικατοπτρίζει τον απομακρυσμένο κλάδο remote/master. Αυτό γίνεται μόνο την πρώτη φορά. Μετά την πρώτη φορά μπορείτε απλά να δώσετε git push για να ωθείτε τις αλλαγές σας.

$ git push --set-upstream origin master
Username for 'https://github.com': jkosta-test
Password for 'https://jkosta-test@github.com': 
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/jkosta-test/myproject.git/'

Γι’ αυτό το λόγο πρέπει να χρησιμοποιήσετε το GitHub Desktop ή κάποιο άλλο γραφικό εργαλείο. Επιλέγοντας το κουμπί Push στο GitHub Desktop (βλ. Εικόνα 4) ωθείτε τις αλλαγές σας στο απομακρυσμένο αποθετήριο github. Επίσης, όλα τα IDEs (π.χ. IntelliJ IDEA, Eclipse, NetBeans, Visual Studio Code) προσφέρουν δυνατότητες ώθησης των αλλαγών σας σε απομακρυσμένα αποθετήρια.

Σημείωση. Η αντίστοιχη εντολή του Mercurial είναι hg push <repository>. Στο mercurial έχετε τη δυνατότητα να δείτε τις εξερχόμενες αλλαγές χωρίς να ενημερώσετε το απομακρυσμένο αποθετήριο με την εντολή hg outgoing που δεν υπάρχει στο git.

Μεταβείτε στο github.com για να δείτε πλέον τις αλλαγές να βρίσκονται εκεί. Πλέον οι αλλαγές σας είναι δημόσιες (public), εκτός κι αν επιλέξατε να δημιουργήσετε ένα ιδιωτικό (private) αποθετήριο.

Η εντολή

$  git branch -vv
  feature1branch 5baedbf Added readme.txt
* master         285480f [origin/master] Merge branch bug1branch

μας δηλώνει ότι ο τοπικός κλάδος master “συνδέεται” με τον origing/master (ο οποίος αντιστοιχεί στον remote/master) και πλέον θα μπορούμε να ωθούμε τις αλλαγές που κάναμε στον τοπικό κλάδο master στον απομακρυσμένο κλάδο remote/master απλά δίνοντας την εντολή git push. Αντιθέτως, ο τοπικός κλάδος feature1branch δεν έχει αντίστοιχο στο απομακρυσμένο αποθετήριο όπως βλέπετε στην έξοδο της παραπάνω εντολής.

Για να μπορέσει να δουλέψει κάποιος άλλος στο αποθετήριό σας και να υποβάλλει αλλαγές, υπάρχουν δυο τρόποι:

  1. να του/της επιτρέψετε να μπορεί να ωθήσει τις αλλαγές του/της στο απομακρυσμένο αποθετήριό σας.
  2. να το κάνει fork (διχαλώσει).
Προσθήκη χρήστη σε απομακρυσμένο αποθετήριο του github

Έχοντας μεταβεί στο απομακρυσμένο αποθετήριό σας (https://github.com/jkosta-test/myproject), (όπου αντί για jkosta-test βλέπετε το δικό σας όνομα χρήστη), ακολουθήστε τα ακόλουθα βήματα:

  1. Κάντε κλικ στα Settings
  2. Επιλέξτε Collaborators από τη λίστα αριστερά
  3. Κλικ στο Add people
  4. Αναζητήστε τον συνεργάτη σας με όνομα, email ή όνομα χρήστη github
  5. Κλικ στο Add to repository

Όπως καταλαβαίνετε, σε δημόσια αποθετήρια είναι πολύ δύσκολο να προσθέτει κανείς κάθε φορά συνεργάτες. Γι’ αυτό το λόγο, υπάρχει κι ο δεύτερος τρόπος συνεργασίας, χωρίς να απαιτείται να προστεθεί ως συνεργάτης.

Προτού όμως αναφερθούμε στο δεύτερο τρόπο, πώς μπορεί τώρα ο συνεργάτης να συνεισφέρει στο απομακρυσμένο αποθετήριο; Ας δούμε τα βήματα που εκτελεί ο συνεργάτης σας ο Νίκος στον Η/Υ του:

$ cd projects
$ git clone https://github.com/jkosta-test/myproject
Cloning into 'myproject'...
remote: Enumerating objects: 19, done.
remote: Counting objects: 100% (19/19), done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 19 (delta 4), reused 19 (delta 4), pack-reused 0 (from 0)
Unpacking objects: 100% (19/19), done.

Στη συνέχεια ο Νίκος δημιουργεί ένα νέο αρχείο README.md με τα περιεχόμενα που φαίνονται παρακάτω και ενημερώνει και το .gitignore για να μπορέσει να δει τις αλλαγές:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   .gitignore

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff .gitignore
diff --git a/.gitignore b/.gitignore
index d847794..16dfc3d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,7 @@
 !readme.txt
 
 # Ignore .md files only in the root folder
-/*.md
+
 
 #Ignore all .java files in folders and subfolders of gen folder
 gen/**/.java

$ more README.md              
# Παράδειγμα αποθετηρίου για το άρθρο https://jkost.github.io/IT/VersionControl/GitIntro/

$ git add .
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   .gitignore
	new file:   README.md

$ git commit -m "Added README.md"
[master c8e249d] Added README.md
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 README.md

και στη συνέχεια ωθεί (push) τις αλλαγές στο απομακρυσμένο αποθετήριο με τη βοήθεια του Github Desktop.

Εικόνα 4 Το Github Desktop.

Πώς όμως μπορείτε να ενημερωθείτε ότι έγινε κάποια αλλαγή στο απομακρυσμένο αποθετήριό σας; Η απάντηση είναι ότι δεν μπορείτε. Το τοπικό και το απομακρυσμένο αποθετήριό σας είναι τελείως ανεξάρτητα. Θα πρέπει μόνοι σας να δώσετε την παρακάτω εντολή από το τοπικό σας αποθετήριο για να ενημερωθεί με τις αλλαγές του Νίκου:

$ git pull
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 4 (delta 1), pack-reused 0 (from 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/jkosta-test/myproject
   285480f..c8e249d  master     -> origin/master
Updating 285480f..c8e249d
Fast-forward
 .gitignore | 2 +-
 README.md  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 README.md

Υπάρχει και η εντολή git fetch. Η διαφορά της από την git pull είναι ότι η πρώτη ενημερώνει μόνο το τοπικό αποθετήριο .git αλλά δεν τροποποιεί τα αρχεία σας μέσα στο myproject (δηλ. δεν θα δείτε π.χ. το νέο αρχείο README.md). Η git pull ενημερώνει και το αποθετήριο .git αλλά και τ’ αρχεία σας στο φάκελο myproject. Υπάρχει και μια ακόμα διαφορά: η εντολή git fetch ανακτά όλους τους κλάδους από το απομακρυσμένο αποθετήριο, δηλ. και τυχόν νέους κλάδους, ενώ η git pull ανακτά μόνο τις αλλαγές στον τρέχοντα κλάδο.

Η παράμετρος -a ή --all μας εμφανίζει όλους τους κλάδους, και τοπικούς και απομακρυσμένους, ενός αποθετηρίου.

$ git branch -a
  feature1branch
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

Όπως βλέπουμε παραπάνω, υπάρχουν δυο τοπικοί κλάδοι, master, feature1branch και ένας απομακρυσμένος κλάδος remotes/origin/master ο οποίος αντιστοιχεί τοπικά στον origin/master.

Ο λόγος που υπάρχει και η pull και η fetch είναι ότι αν για κάποιο λόγο δεν είστε ευχαριστημένοι με τις αλλαγές, μπορείτε πάντα να γυρίσετε σε μια προηγούμενη έκδοση χωρίς να έχετε ενημερώσει τα αρχεία σας στον τοπικό φάκελο.

Σημείωση. Οι αντίστοιχες εντολές του Mercurial είναι hg fetch <repository> και hg pull <repository>. Ισχύει: hg pull = hg fetch + hg update. Στο git git pull = git fetch + git merge, δηλ. συγχωνεύει τον απομακρυσμένο κλάδο (π.χ. origin/master) με τον τοπικό κλάδο (π.χ. master). Υπάρχει και η εντολή hg pull -u που ενημερώνει αυτόματα τον τοπικό σας φάκελο. Στο mercurial έχετε τη δυνατότητα να δείτε τις εισερχόμενες αλλαγές χωρίς να ενημερώσετε το αποθετήριό σας .hg με την εντολή hg incoming που αντίστοιχή της δεν υπάρχει στο git.

Διχάλωση (forking) απομακρυσμένου αποθετηρίου

Με αυτόν τον τρόπο μπορεί ο καθένας να συνεισφέρει στο απομακρυσμένο αποθετήριο χωρίς να είναι συνεργάτης (collaborator) του απομακρυσμένου αποθετηρίου σας.

  1. Ο συνεργάτης (Νίκος) πρέπει από το λογαριασμό του στο github να μεταβεί στην ιστοσελίδα https://github.com/jkosta-test/myproject και να πατήσει στο κουμπί Fork (δίνοντας το ίδιο όνομα έργου). Με αυτόν τον τρόπο έχει κλωνοποιηθεί το απομακρυσμένο αποθετήριο στο δικό του λογαριασμό github. Πλέον μπορεί να κλωνοποιήσει το διχαλωμένο απομακρυσμένο αποθετήριο τοπικά:
$ git clone https://github.com/<nikos_username>/myproject
Cloning into 'myproject'...
remote: Enumerating objects: 23, done.
remote: Counting objects: 100% (23/23), done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 23 (delta 5), reused 23 (delta 5), pack-reused 0 (from 0)
Unpacking objects: 100% (23/23), done.
$ git log --oneline --all --graph             
* c8e249d (HEAD -> master, origin/master, origin/HEAD) Added README.md
| * 5baedbf (feature1branch) Added readme.txt
|/  
*   285480f Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Στη συνέχεια κάνει τις αλλαγές του τοπικά και τις ωθεί στο απομακρυσμένο αποθετήριό του.

$ git diff 
diff --git a/README.md b/README.md
index ac52871..a1b464d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-# Παράδειγμα αποθετηρίου για το άρθρο https://jkost.github.io/IT/VersionControl/GitIntro/
+# Παράδειγμα αποθετηρίου για το άρθρο [Εισαγωγή στο Git](https://jkost.github.io/IT/VersionControl/GitIntro/).
$ git add .
$ git commit -m "Updated README.md"
[master 9710ef2] Updated README.md
 1 file changed, 1 insertion(+), 1 deletion(-)

Στη συνέχεια Push στο διχαλωμένο απομακρυσμένο αποθετήριο μέσω του Github Desktop. Πώς όμως θα ενημερώσει το αρχικό απομακρυσμένο αποθετήριο το οποίο διχάλωσε (fork);

Όταν ανανεώσει τη σελίδα του έργου του στο github θα δει:

This branch is 1 commit ahead of jkosta-test/myproject:master.

Το github παρέχει την αίτηση συγχώνευσης (pull request ή αλλιώς merge request όπως αναφέρεται σε άλλα απομακρυσμένα αποθετήρια). Κάντε κλικ στο κουμπί Contribute και στη συνέχεια στο Open pull request.

Εικόνα 5 Pull request στο Github.

Παρατηρήστε ότι το αίτημα συγχώνευσης είναι από το δικό σας απομακρυσμένο αποθετήριο (<your_username>/myproject) στο αρχικό (jkosta-test/myproject). Πατήστε Create pull request.

Στο αρχικό πλέον αποθετήριο (https://github.com/jkosta-test/myproject) θα δείτε ότι υπάρχει ένα νέο Pull request. Επιλέγοντας το Pull requests θα δείτε μια λίστα με τα ανοιχτά αιτήματα συγχώνευσης. Επιλέξτε το Updated README. md pull request και πατήστε στο Merge pull request και Confirm merge. Πλέον οι αλλαγές του συνεργάτη σας Νίκου έχουν συγχωνευθεί στο απομακρυσμένο αποθετήριό σας. Μένει να κάνετε git pull για να τις μεταφέρετε στο τοπικό σας αποθετήριο.

$ git pull
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 3 (delta 1), pack-reused 0 (from 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/jkosta-test/myproject
   c8e249d..909ba00  master     -> origin/master
Updating c8e249d..909ba00
Fast-forward
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --all --graph
*   909ba00 (HEAD -> master, origin/master, origin/HEAD) Merge pull request #1 from jkost/master
|\  
| * 9710ef2 Updated README.md
|/  
* c8e249d Added README.md
| * 5baedbf (feature1branch) Added readme.txt
|/  
*   285480f Merge branch bug1branch
|\  
| * 00937c5 Fixed hello.c
* | 2946b52 Modified hello.c
|/  
* df85b3f Updated goodbye.c
* bd054a7 Added .gitignore
* 245fa8f Updated hello.c
* 488273f Initial commit

Επίλογος

Σε αυτό το άρθρο δώσαμε μια εισαγωγή στο Git και στο Github. Μάθαμε για την ιστορία των ΣΔΕ και του ίδιου του Git, τις βασικές εντολές για να δημιουργήσουμε αποθετήρια, να κλωνοποιήσουμε απομακρυσμένα αποθετήρια (π.χ. από το Github), να υποβάλλουμε τις αλλαγές μας τοπικά (commit), αλλά και στα απομακρυσμένα αποθετήρια (push), για το πόσο εύκολο είναι να δημιουργήσουμε κλάδους (branches) και να τους συγχωνεύσουμε με τον κύριο (master branch), πώς να αναιρέσουμε τις αλλαγές μας, κλπ.

Συμπερασματικά, το Git διακρίνεται για την αμεσότητα και την ευκολία χρήσης και υπερτερεί σε σχέση με άλλα συστήματα διαχείρισης εκδόσεων τόσο σε απόδοση όσο και σε επεκτασιμότητα. Δεν χρειάζεται συντήρηση, ούτε διαχειριστή, και δουλεύει καλά με κάθε είδους προγραμματιστές και κάθε είδους έργα.

Πηγές

  1. https://git-scm.com
  2. List of version control software
  3. Atlassian, Become a Git Guru.
  4. Chacon S. & Straub B. (2014), Pro Git, 2nd Ed., Apress.
  5. Gandhi R. (2022), Head First Git, O’Reilly.
  6. Hodson R. (2012), Git Succintly, Syncfusion.
  7. Pardo J. (2016), Git Tutorial, JavaCodeGeeks.
  8. Raymond R., Understanding Version-Control Systems.
  9. Rosenbaum O.(2023), The Git Merge Handbook – Definitive Guide to Merging in Git.
  10. Tsitoara M. (2020), Beginning Git and GitHub, APress.
  11. Codecademy, Learn Git.
  12. Codeschool, Git Real.
  13. Codeschool, Git Real 2.
  14. Codeschool, Mastering Github.
  15. Git® Notes for Professionals, GoalKicker
  16. 15 Git Branch Command Examples to Create and Manage Branches
  17. Git Branching - Basic Branching and Merging
  18. Git Complete: The definitive, step-by-step guide to Git, Udemy
  19. The Ultimate GIT 5-day Challenge, Udemy
  20. Git Fundamentals, Pluralsight
  21. Git Started with GitHub, Udemy
  22. Getting Started With Git: Key Concepts for Beginners, Udemy
  23. Command Line Essentials: Git Bash for Windows, Udemy
  24. Short and Sweet: Get Started with Git and GitHub Right Now, Udemy