-
1. Ξεκινώντας με το Git
-
2. Τα θεμελιώδη στοιχεία του Git
-
3. Διακλαδώσεις στο Git
-
4. Το Git στον διακομιστή
- 4.1 Τα πρωτόκολλα
- 4.2 Εγκατάσταση του Git σε διακομιστή
- 4.3 Δημιουργία δημόσιου κλειδιού SSH
- 4.4 Στήσιμο του διακομιστή
- 4.5 Δαίμονες του Git
- 4.6 Έξυπνο HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Επιλογές φιλοξενίας από τρίτους
- 4.10 Ανακεφαλαίωση
-
5. Κατανεμημένο Git
-
6. GitHub
-
7. Εργαλεία του Git
- 7.1 Επιλογή αναθεώρησης
- 7.2 Διαδραστική εργασία με το στάδιο καταχώρισης
- 7.3 Αποθέματα και Καθαρισμός
- 7.4 Υπογραφή της δουλειάς μας
- 7.5 Αναζήτηση
- 7.6 Η ιστορία ξαναγράφεται
- 7.7 Απομυθοποίηση της reset
- 7.8 Προχωρημένη Συγχώνευση
- 7.9 Rerere
- 7.10 Αποσφαλμάτωση με το Git
- 7.11 Υπομονάδες
- 7.12 Δεμάτιασμα δεδομένων
- 7.13 Replace
- 7.14 Αποθήκευση διαπιστευτηρίων
- 7.15 Ανακεφαλαίωση
-
8. Εξατομίκευση του Git
-
9. Το Git και άλλα συστήματα
- 9.1 Το Git ως πελάτης
- 9.2 Μετανάστευση στο Git
- 9.3 Ανακεφαλαίωση
-
10. Εσωτερική λειτουργία του Git
- 10.1 Διοχετεύσεις και πορσελάνες
- 10.2 Αντικείμενα του Git
- 10.3 Αναφορές του Git
- 10.4 Πακετάρισμα αρχείων
- 10.5 Τα refspec
- 10.6 Πρωτόκολλα μεταφοράς
- 10.7 Διατήρηση και ανάκτηση δεδομένων
- 10.8 Μεταβλητές περιβάλλοντος
- 10.9 Ανακεφαλαίωση
-
A1. Appendix A: Το Git σε άλλα περιβάλλοντα
- A1.1 Γραφικές διεπαφές
- A1.2 Το Git στο Visual Studio
- A1.3 Git στο Visual Studio Code
- A1.4 Git στο IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git στο Sublime Text
- A1.6 Το Git στο Bash
- A1.7 Το Git στο Zsh
- A1.8 Το Git στο Powershell
- A1.9 Ανακεφαλαίωση
-
A2. Appendix B: Ενσωμάτωση του Git στις εφαρμογές μας
- A2.1 Γραμμή εντολών Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Appendix C: Εντολές Git
- A3.1 Ρύθμιση και διαμόρφωση
- A3.2 Λήψη και δημιουργία έργων
- A3.3 Βασική λήψη στιγμιοτύπων
- A3.4 Διακλάδωση και συγχώνευση
- A3.5 Κοινή χρήση και ενημέρωση έργων
- A3.6 Επιθεώρηση και σύγκριση
- A3.7 Αποσφαλμάτωση
- A3.8 Επιθέματα
- A3.9 Ηλεκτρονικό ταχυδρομείο
- A3.10 Εξωτερικά Συστήματα
- A3.11 Διοίκηση
- A3.12 Εντολές διοχέτευσης
7.11 Εργαλεία του Git - Υπομονάδες
Υπομονάδες
Συχνά συμβαίνει ότι καθώς εργαζόμαστε σε ένα έργο, θέλουμε να χρησιμοποιήσουμε ένα άλλο έργο μέσα από αυτό. Ίσως πρόκειται για μια βιβλιοθήκη που έχει αναπτυχθεί από τρίτους ή ότι αναπτύσσουμε ξεχωριστά και χρησιμοποιούμε πολλά γονικά έργα-γονείς. Ένα κοινό ζήτημα προκύπτει σε αυτά τα σενάρια: θέλουμε αφενός τα δύο έργα να αντιμετωπίζονται ως ξεχωριστά, αφετέρου να μπορούμε να χρησιμοποιούμε το ένα μέσα από το άλλο.
Ακολουθεί ένα παράδειγμα. Ας υποθέσουμε ότι αναπτύσσουμε έναν ιστότοπο και δημιουργούμε τροφοδοσίες Atom. Αντί να γράφουμε τον δικό μας κώδικα δημιουργίας Atom, αποφασίζουμε να χρησιμοποιήσουμε μια βιβλιοθήκη. Είναι πιθανό να χρειάζεται είτε να συμπεριλάβουμε αυτόν τον κώδικα από μια κοινόχρηστη βιβλιοθήκη όπως μια εγκατάσταση CPAN ή Ruby gem είτε να αντιγράψουμε τον πηγαίο κώδικα στο δικό μας δέντρο έργου. Το πρόβλημα με τη συμπερίληψη της βιβλιοθήκης είναι ότι είναι δύσκολο να προσαρμόσουμε τη βιβλιοθήκη με οποιονδήποτε τρόπο και συχνά πιο δύσκολο να την αναπτύξουμε, επειδή πρέπει να βεβαιωθούμε ότι κάθε πελάτης διαθέτει αυτήν τη βιβλιοθήκη. Το πρόβλημα με την ενσωμάτωση του κώδικα στο δικό μας έργο είναι ότι τυχόν εξατομικευμένες αλλαγές που κάνουμε είναι δύσκολο να συγχωνευθούν, όταν υπάρχουν διαθέσιμες αλλαγές στο απομκα ρεύμα (upstream).
Το Git αντιμετωπίζει αυτό το πρόβλημα χρησιμοποιώντας υπομονάδες (submodules). Οι υπομονάδες μας επιτρέπουν να διατηρούμε ένα αποθετήριο Git ως υποκατάλογο ενός άλλου αποθετηρίου Git. Αυτό μας επιτρέπει να κλωνοποιήσουμε ένα άλλο αποθετήριο στο έργο μας και να κρατήσουμε τις υποβολές μας ξεχωριστά.
Ξεκινώντας με τις υπομονάδες
Θα δούμε βήμα-βήμα την ανάπτυξη ενός απλού έργου που έχει διασπαστεί σε ένα κύριο έργο και σε μερικά υπο-έργα.
Ας αρχίσουμε προσθέτοντας ένα υπάρχον αποθετήριο Git ως λειτουργική υπομονάδα του αποθετηρίου στο οποίο εργαζόμαστε.
Για να προσθέσουμε μία νέα υπομονάδα χρησιμοποιούμε την εντολή git submodule add με την απόλυτη ή σχετική διεύθυνση URL του έργου που θέλουμε να αρχίσουμε να παρακολουθούμε.
Σε αυτό το παράδειγμα, θα προσθέσουμε μια βιβλιοθήκη που ονομάζεται “DbConnector”.
$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Εκ προεπιλογής, οι υπομονάδες θα προσθέσουν το υποέργο σε έναν κατάλογο που ονομάζεται το ίδιο με τον αποθετήριο, στην περίπτωση αυτή “DbConnector”. Μπορούμε να προσθέσουμε μια διαφορετική διαδρομή στο τέλος της εντολής, εάν θέλουμε να πάει αλλού.
Εάν εκτελέσουμε την git status σε αυτό το σημείο, θα παρατηρήσουμε μερικά πράγματα.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: DbConnector
Πρώτα θα πρέπει να παρατηρήσουμε το νέο αρχείο .gitmodules.
Αυτό είναι ένα αρχείο διαμόρφωσης που αποθηκεύει την αντιστοίχιση μεταξύ της διεύθυνσης URL του έργου και του τοπικού υποκαταλόγου στον οποίο το έχουμε έλξει:
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
Εάν έχουμε πολλές υπομονάδες, θα έχουμε πολλαπλές καταχωρήσεις σε αυτό το αρχείο.
Είναι σημαντικό να σημειωθεί ότι η έκδοση και αυτού του αρχείου ελέγχεται όπως και τα άλλα αρχεία ή το αρχείο .gitignore.
Ωθείται και έλκεται όπως και το υπόλοιπο του έργο μας.
Αυτός είναι ο τρόπος με τον οποίο αυτοί που κλωνοποιούν αυτό το έργο γνωρίζουν από πού να πάρουν τα έργα υπομονάδων.
|
Note
|
Δεδομένου ότι η διεύθυνση URL στο αρχείο .gitmodules είναι αυτή που θα προσπαθήσουν πρώτα να κλωνοποιήσουν/ανακτήσουν οι άλλοι χρήστες, πρέπει να βεβαιωθούμε ότι χρησιμοποιούμε μια διεύθυνση URL στην οποιία έχουν πρόσβαση, εάν είναι δυνατόν.
Για παράδειγμα, αν ωθούμε σε διαφορετική διεύθυνση URL από αυτήν από την οποία έλκουν οι άλλοι, καλό είναι να χρησιμοποιούμε αυτήν στην οποία έχουν πρόσβαση οι άλλοι.
Μπορούμε να αντικαταστήσουμε αυτήν την τιμή τοπικά με την |
Η άλλη λίστα στην έξοδο της git status είναι η καταχώρηση του καταλόγου του έργου.
Εάν εκτελέσουμε την git diff σε αυτήν, βλέπουμε κάτι ενδιαφέρον:
$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
Αν και DbConnector είναι ένας υποκατάλογος στον κατάλογο εργασίας μας, το Git τον βλέπει ως υπομονάδα και δεν παρακολουθεί τα περιεχόμενά του όταν δεν βρισκόμαστε σε αυτόν τον κατάλογο.
Αντίθετα το Git τον βλέπει ως μια συγκεκριμένη υποβολή από αυτό το αποθετήριο.
Αν θέλουμε λίγο καλύτερη έξοδο diff, μπορούμε να περάσουμε την επιλογή --submodule στην git diff.
$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+ path = DbConnector
+ url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)
Όταν υποβάλλουμε, βλέπουμε κάτι τέτοιο:
$ git commit -am 'Add DbConnector module'
[master fb9093c] Add DbConnector module
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 DbConnector
Παρατηρούμε τη λειτουργία 160000 για την καταχώρηση DbConnector.
Αυτή είναι μια ειδική λειτουργία στο Git που ουσιαστικά σημαίνει ότι καταγράφουμε μια υποβολή ως καταχώρηση καταλόγου και όχι ως υποκατάλογο ή αρχείο.
Τέλος, ωθούμε αυτές τις αλλαγές:
$ git push origin master
Κλωνοποίηση έργου με υπομονάδες
Εδώ θα κλωνοποιήσουμε ένα έργο που περιέχει μία υπομονάδα. Όταν κλωνοποιούμε ένα τέτοιο έργο, εκ προεπιλογής εμφανίζονται οι κατάλογοι που περιέχουν υπομονάδες, αλλά κανένα από τα αρχεία μέσα σε αυτούς ακόμα:
$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r--r-- 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
$
Ο κατάλογος DbConnector είναι εκεί, αλλά κενός.
Πρέπει να εκτελέσουμε δύο εντολές: την git submodule init για να αρχικοποιήσουμε το τοπικό μας αρχείο διαμόρφωσης και την git submodule update για να ανακτήσουμε όλα τα δεδομένα από το έργο και να μεταβούμε στην κατάλληλη υποβολή που αναφέρεται στο υπερ-έργο μας:
$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
Τώρα ο υποκατάλογος DbConnector βρίσκεται στην ακριβή κατάσταση που ήταν όταν υποβάλλαμε νωρίτερα.
Υπάρχει ένας άλλος τρόπος για να γίνει αυτό, ο οποίος είναι μάλιστα λίγο πιο απλός.
Αν περάσουμε την επιλογή --recurse-submodules στην εντολή git clone, θα αρχικοποιήσει και ενημερώσει κάθε υπομονάδα στο αποθετήριο αυτόματα.
$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
Αν έχουμε ήδη κλωνοποιήσει το έργο και ξεχάσαμε την επιλογή --recurse-submodules, μπορούμε να συνδυάσουμε τα βήματα git submodule init και git submodule update εκτελώντας git submodule update --init.
Για να αρχικοποιήσουμε, έλξουμε και να ενημερώσουμε οποιοδήποτε εμφωλευμένη υπομονάδα (submodule), μπορούμε να χρησιμοποιήσουμε την ολοκληρωμένη εντολή git submodule update --init --recursive.
Εργασία σε έργο με υπομονάδες
Τώρα έχουμε αντίγραφο ενός έργου με υπομονάδες και θα δουλέψουμε με τους συνεργάτες μας τόσο για το κύριο έργο όσο και για τα έργα των υπομονάδων.
Έλξη των απομακρυσμένων αλλαγών από την απομακρυσμένη υπομονάδα
Το απλούστερο μοντέλο χρήσης υπομονάδων σε ένα έργο είναι αν απλά καταναλώνουμε ένα υποέργο και θέλουμε να λαμβάνουμε ενημερώσεις από αυτό κάπου-κάπου αλλά στην πραγματικότητα δεν τροποποιούμε τίποτα όταν ανακτούμε τις ενημερώσεις. Ας δούμε ένα απλό σχετικό παράδειγμα.
Αν θέλουμε να ελέγξουμε για νέα εργασία σε μία υπομονάδα, μπορούμε να μεταβούμε στον κατάλογο και να εκτελέσουμε git fetch και git merge για να ενημερώσουμε τον τοπικό κώδικα.
$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
Τώρα, αν επιστρέψουμε στο κύριο έργο και εκτελέσουμε την git diff --submodule, μπορούμε να δούμε ότι η υπομονάδα ενημερώθηκε και να πάρουμε μια λίστα υποβολών που προστέθηκαν σε αυτήν.
Εάν δεν θέλουμε να πληκτρολογούμε --submodule κάθε φορά που τρέχουμε την git diff, μπορούμε να την ορίσουμε ως προεπιλεγμένη μορφή ρυθμίζοντας την τιμή της diff.submodule στο “log”.
$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
> more efficient db routine
> better connection routine
Εάν υποβάλλούμε σε αυτό το σημείο, τότε θα κλειδώσουμε την υπομονάδα να έχει τον νέο κώδικα όταν τον ενημερώνουν οι άλλοι.
Πάλι υπάρχει ένας ευκολότερος τρόπος για να γίνει αυτό, αν προτιμάμε να μην ανακτούμε και συγχωνεύουμε, με μη αυτόματο τρόπο τον υποκατάλογο.
Εάν εκτελέσουμε την git submodule update --remote, το Git θα μεταβεί στις υπομονάδες μας, θα ανακτήσει και θα ενημερώσει για μας.
$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'
Αυτή η εντολή θα υποθέσει εκ προεπιλογής ότι θέλουμε να ενημερώσουμε τον προεπιλεγμένο κλάδο του απομακρυσμένου αποθετηρίου υπομονάδων (αυτόν τον κλάδο που δείχνει ο HEAD στο απομακρυσμένο).
Ωστόσο, μπορούμε να το ορίσουμε να είναι κάτι διαφορετικό, εφόσον το θέλουμε.
Για παράδειγμα, εάν θέλουμε η υπομονάδα DbConnector να παρακολουθεί τον κλάδο “stable” του αποθετηρίου, μπορούμε να το ορίσουμε είτε στο αρχείο .gitmodules (έτσι ώστε όλοι οι άλλοι να μπορούν να το παρακολουθούν), είτε στο τοπικό αρχείο .git/config.
Ας το ορίσουμε στο αρχείο .gitmodules:
$ git config -f .gitmodules submodule.DbConnector.branch stable
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'
Αν παραλείψουμε την επιλογή -f .gmodmodules, θα κάνει μόνο την αλλαγή για μας, αλλά πιθανότατα είναι πιο λογικό να παρακολουθούμε αυτές τις πληροφορίες με το αποθετήριο, ώστε και όλοι οι άλλοι να το κάνουν.
Αν τρέξουμε git status σε αυτό το σημείο, το Git θα μας δείξει ότι έχουμε “νέες υποβολές” στην υπομονάδα.
$ 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 checkout -- <file>..." to discard changes in working directory)
modified: .gitmodules
modified: DbConnector (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
Εάν ορίσουμε τη ρύθμιση διαμόρφωσης status.submodulesummary, το Git θα μας δείξει επίσης μια σύντομη περίληψη των αλλαγών στις υπομονάδες μας:
$ git config status.submodulesummary 1
$ 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 checkout -- <file>..." to discard changes in working directory)
modified: .gitmodules
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c3f01dc...c87d55d (4):
> catch non-null terminated lines
Σε αυτό το σημείο, αν εκτελέσουμε την git diff, μπορούμε να δούμε τόσο ότι έχουμε τροποποιήσει το αρχείο .gitmodules όσο και ότι υπάρχουν αρκετές υποβολές που έχουμε έλξει και είμαστε έτοιμοι να υποβάλλουμε για στην υπομονάδα του έργου μας.
$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
Αυτό είναι πολύ ωραίο, καθώς μπορούμε πραγματικά να δούμε το μητρώο υποβολών που πρόκειται να υποβάλλούμε στην υπομονάδα μας.
Αφού υποβληθούν, μπορούμε να δούμε αυτές τις πληροφορίες και μετά την υποβολή όταν τρέχουμε την git log -p.
$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Sep 17 16:37:02 2014 +0200
updating DbConnector for bug fixes
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
Το Git θα προσπαθήσει εκ προεπιλογής να ενημερώσει όλες τις υπομονάδες μας όταν τρέχουμε την git submodule update --remote.
Αν έχουμε πολλές από αυτές, ίσως θέλουμε να περάσουμε το όνομα μόνο της υπομονάδας που θέλουμε να ενημερώσουμε.
Έλξη Απομακρυσμένων Αλλαγών από το Απομακρυσμένο Έργο
Ας πάρουμε την θέση του συνεργάτη μας, ο οποίος έχει το δικό του τοπικό κλώνο από το MainProject αποθετήριο.
Εκτελώντας απλά git pull για να πάρει τις νέες μας υποβληθείσες αλλαγές δεν είναι αρκετό:
$ git pull
From https://github.com/chaconinc/MainProject
fb9093c..0a24cfc master -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
c3f01dc..c87d55d stable -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
.gitmodules | 2 +-
DbConnector | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
$ 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 checkout -- <file>..." to discard changes in working directory)
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c87d55d...c3f01dc (4):
< catch non-null terminated lines
< more robust error handling
< more efficient db routine
< better connection routine
no changes added to commit (use "git add" and/or "git commit -a")
Εκ προεπιλογής, η εντολή git pull αναδρομικά έλκει αλλαγές από τις υπομονάδες (submodules), όπως μπορούμε να δούμε από το αποτέλεσμα της πρώτης εντολής παραπάνω.
Ωστόσο, δεν ενημερώνει τις υπομονάδες.
Αυτό φαίνεται από το αποτέλεσμα της εντολής git status, όπου δείχνει ότι η υπομονάδα είναι “επεξεργασμένη” (“modified”), και έχει “νέες υποβολές”.
Επιπλέον, οι αγκύλες που απεικονίζουν τις νέες υποβολές να δείχνουν αριστερά (<), υποδεικνύοντας ότι αυτές οι υποβολές είναι εγγεγραμένες μέσα στο MainProject αλλά δεν είναι παρών στη τοπική ενημέρωση του DbConnector.
Για να ολοκληρώσουμε την ενημέρωση, πρέπει να τρέξουμε git submodule update:
$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
Σημειώνουμε ότι για να είμαστε σε ασφαλής θέση, θα πρέπει να τρέξουμε git submodule update με την επιλογή --init σε περίπτωση που οι υποβολές του MainProject που μόλις έλξαμε πρόσθεσαν νέες υπομονάδες, και με την επιλογή --recursive αν οποιαδήποτε υπομονάδα έχει εμφωλευμένες υπομονάδες.
Αν θέλουμε να αυτοματοποιήσουμε αυτή τη διαδικασία, μπορούμε να προσθέσουμε την επιλογή --recurse-submodules στην εντολή git pull (από την έκδοση Git 2.14).
Αυτό θα κάνει το Git να τρέξει git submodule update αμέσως μετά την έλξη, βάζοντας τις υπομονάδες στη σωστή κατάσταση.
Επιπλέον, αν θέλουμε να κάνουμε το Git πάντα να έλκει με την επιλογή --recurse-submodules, μπορούμε να ορίσουμε την επιλογή submodule.recurse σε true (αυτό δουλεύει για την git pull από την έκδοση Git 2.15).
Αυτή η επιλογή θα κάνει το Git να χρησιμοποιήσει την επιλογή --recurse-submodules για όλες τις εντολές που την υποστηρίζουν (εκτός της clone).
Υπάρχει μια ιδιαίτερη κατάσταση που μπορεί να συμβεί ότι έλκουμε ενημερώσεις υπερ-έργων: μπορεί το απομακρυσμένο αποθετήριο να έχει αλλάξει το URL της υπομονάδας στο αρχείο .gitmodules σε κάποια από τις υποβολές που έλκουμε.
Αυτό, για παράδειγμα, μπορεί να συμβεί αν το έργο υπομονάδας αλλάξει πλατφόρμα οπου φιλοξενείται.
Σε αυτή την περίπτωση, είναι πιθανό για τις εντολές git pull --recurse-submodules, ή git submodule update, να αποτύχουν αν το υπερ-έργο αναφέρεται σε μια υποβολή της υπομονάδας η οποία δεν βρίσκεται στην απομακρυσμένη υπομονάδα, που έχει οριστεί τοπικά στο αποθετήριό μας.
Για να επιλύσουμε αυτή την κατάσταση, η εντολή git submodule sync απαιτείται:
# copy the new URL to your local config
$ git submodule sync --recursive
# update the submodule from the new URL
$ git submodule update --init --recursive
Εργασία σε υπομονάδα
Είναι πολύ πιθανό ότι αν χρησιμοποιούμε υπομονάδες, το κάνουμε επειδή θέλουμε να εργαστούμε στον κώδικα στην υπομονάδα και ταυτόχρονα εργαζόμαστε στον κώδικα στο κύριο έργο (ή σε πολλές υπομονάδες). Διαφορετικά, πιθανότατα θα χρησιμοποιούσαμε ένα απλούστερο σύστημα διαχείρισης εξαρτήσεων (όπως τα Maven ή Rubygems).
Τώρα, λοιπόν, να δούμε ένα παράδειγμα στο οποίο κάνουμε αλλαγές στην υπομονάδα ταυτόχρονα με το κύριο έργο και υποβάλλουμε και δημοσιεύουμε αυτές τις αλλαγές ταυτόχρονα.
Μέχρι στιγμής, όταν εκτελούσαμε την εντολή git submodule update για να ανακτήσουμε τις αλλαγές από τα αποθετήρια υπομονάδων, το Git έπαιρνε τις αλλαγές και ενημέρωνε τα αρχεία στον υποκατάλογο, αλλά θα αφήσει το υπό-αποθετήριο σε μία κατάσταση που ονομάζεται κατάσταση με αποσπασμένο HEAD (“detached HEAD”).
Αυτό σημαίνει ότι δεν υπάρχει τοπικός κλάδος εργασίας (όπως ο master για παράδειγμα) που παρακολουθεί τις αλλαγές.
Χωρίς κλάδο εργασίας να παρακολουθεί τις αλλαγές, αυτό σημαίνει πως ακόμα και αν υποβάλουμε αλλαγές στην υπομονάδα, αυτές οι αλλαγές είναι πολύ πιθανό να χαθούν την επόμενη φορά που θα τρέξουμε git submodule update.
Θα πρέπει να κάνουμε κάποια επιπλέον βήματα αν θέλουμε οι αλλαγές στην υπομονάδα να παρακολουθούνται.
Προκειμένου να ρυθμίσουμε την υπομονάδα μας ώστε να είναι ευκολότερο να μπούμε και να την τροποποίησουμε, πρέπει να κάνουμε δύο πράγματα.
Πρέπει να πάμε σε κάθε υπομονάδα και να μεταβούμε σε έναν κλάδο στον οποίο θα εργαστούμε.
Στη συνέχεια πρέπει να πούμε στο Git τι να κάνει, αν έχουμε κάνει αλλαγές και μετά η git submodule update --remote έλξει όποια καινούρια δουλειά που έγινε στο απομακρυσμένο ρεύμα (upstream).
Οι επιλογές είναι ότι μπορούμε να τις συγχωνεύσουμε στην τοπική δουλειά μας ή να δοκιμάσουμε να αλλάξουμε τη βάση της τοπικής εργασίας μας στις νέες αλλαγές.
Καταρχάς, ας πάμε στον κατάλογο υπομονάδων μας και ας μεταβούμε σε κάποιον κλάδο.
$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'
Ας το δοκιμάσουμε να ενημερώσουμε την υπομονάδα με την επιλογή “--merge”.
Για να το καθορίσουμε χειροκίνητα, μπορούμε απλά να προσθέσουμε την επιλογή --merge στην κλήση της `update'.
Εδώ θα δούμε ότι υπήρξε μια αλλαγή στον διακομιστή για αυτην την υπομονάδα και συγχωνεύεται.
$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
c87d55d..92c7337 stable -> origin/stable
Updating c87d55d..92c7337
Fast-forward
src/main.c | 1 +
1 file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'
Εάν εισέλθουμε στον κατάλογο DbConnector, έχουμε τις νέες αλλαγές που έχουν ήδη συγχωνευθεί στον τοπικό μας κλάδο stable.
Τώρα ας δούμε τι συμβαίνει όταν κάνουμε τη δική μας τοπική αλλαγή στη βιβλιοθήκη και κάποιος άλλος ωθεί μια άλλη αλλαγή στο απομακρυσμένο ρέυμα (upstream) την ίδια στιγμή.
$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'unicode support'
[stable f906e16] unicode support
1 file changed, 1 insertion(+)
Τώρα αν ενημερώσουμε την υπομονάδα μας, μπορούμε να δούμε τι συμβαίνει όταν έχουμε κάνει μια τοπική αλλαγή και υπάρχει επίσης μια αλλαγή που πρέπει να έλξουμε και ενσωματώσουμε.
$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: Unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
Εάν ξεχάσουμε τις επιλογές --rebase ή --merge, το Git απλά θα ενημερώσει την υπομονάδα σε ό,τι βρίσκεται στον διακομιστή και θα επαναφέρει το έργο μας σε κατάσταση αποσπασμένου HEAD.
$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
Αν συμβεί κάτι τέτοιο, δεν χρειάζεται να ανησυχούμε, μπορούμε απλά να επιστρέψουμε στον κατάλογο και να μεταβούμε ξανά στον κλάδο μας (που θα εξακολουθεί να περιέχει τη δουλειά μας) και να συγχωνεύσουμε ή να επανατοποθετήσουμε τον origin/stable (ή όποιον απομακρυσμένο κλάδο θέλουμε) χειροκίνητα.
Αν δεν έχουμε υποβάλει τις αλλαγές στην υπομονάδα μας και εκτελέσουμε μια ενημέρωση υπομονάδας, που θα προκαλούσε προβλήματα, το Git θα ανακτήσει τις αλλαγές αλλά δεν θα αντικαταστήσει μη-αποθηκευμένη εργασία στον κατάλογο υπομονάδων μας.
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
5d60ef9..c75e92a stable -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
Εάν κάναμε αλλαγές που συγκρούνται με κάτι που άλλαξε στο upstream, το Git θα μας ενημερώσει όταν εκτελούμε την ενημέρωση.
$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
Μπορούμε να μεταβούμε στον κατάλογο υπομονάδων και να διορθώσουμε τη σύγκρουση όπως θα κάναμε κανονικά.
Δημοσίευση αλλαγών σε υπομονάδες
Τώρα έχουμε κάποιες αλλαγές στον κατάλογο υπομονάδων μας. Ορισμένες από αυτές εισήχθησαν από τις προηγούμενες ενημερώσεις μας από το upstream και άλλες έγιναν σε τοπικό επίπεδο και δεν είναι διαθέσιμες σε κανέναν ακόμα επειδή δεν τις έχουμε ωθήσει ακόμα.
$ git diff
Submodule DbConnector c87d55d..82d2ad3:
> Merge from origin/stable
> Update setup script
> Unicode support
> Remove unnecessary method
> Add new option for conn pooling
Εάν υποβάλλουμε στο κύριο έργο και το ωθήσουμε χωρίς να ωθήσουμε τις αλλαγές της υπομονάδας, άλλοι που προσπαθούν να κάνουν ενημέρωση (checkout) με τις αλλαγές μας θα έχουν πρόβλημα, αφού δεν θα έχουν τρόπο να πάρουν τις αλλαγές της υπομονάδας από τις οποίες εξαρτώνται. Αυτές οι αλλαγές θα υπάρχουν μόνο στο τοπικό αντίγραφό μας.
Για να βεβαιωθούμε ότι αυτό δεν συμβαίνει, μπορούμε να ζητήσουμε από το Git να ελέγξει ότι όλες οι υπομοναδες μας έχουν ωθηθεί σωστά πριν ωθήσουμε στο κύριο έργο.
Η εντολή git push παίρνει το όρισμα --recurse-submodules το οποίο μπορεί να τεθεί είτε σε “check” είτε σε “on-demand”.
Η επιλογή “check” θα κάνει την push να αποτύχει εάν κάποια από τις υποβαλλόμενες αλλαγές της υπομονάδας δεν έχει ωθηθεί.
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
Όπως βλέπουμε, μας δίνει επίσης κάποιες χρήσιμες συμβουλές για το τι θα θέλαμε να κάνουμε στη συνέχεια.
Η απλή επιλογή είναι να μπούμε σε κάθε υπομονάδα και να ωθήσουμε χειροκίνητα στα απομακρυσμένα αποθετήρια για να βεβαιωθούμε ότι είναι εξωτερικά διαθέσιμα και στη συνέχεια να δοκιμάσουμε αυτήν την ώθηση ξανά.
Αν θέλουμε την “check” συμπεριφορά να γίνεται σε όλες τις ωθήσεις, μπορούμε να κάνουμε αυτή τη συμπεριφορά προεπιλεγμένη εκτελώντας git config push.recurseSubmodules check.
Η άλλη επιλογή είναι να χρησιμοποιήσουμε την τιμή “on-demand”, η οποία θα προσπαθήσει να το κάνει αντί για μας.
$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
c75e92a..82d2ad3 stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
3d6d338..9a377d1 master -> master
Όπως βλέπουμε, το Git μπήκε στη μονάδα DbConnector και την ώθησε πριν ωθήσει στο κύριο έργο.
Εάν αυτή η ώθηση υπομονάδας αποτύχει για κάποιο λόγο, η ώθηση στο κύριο έργο θα αποτύχει επίσης.
Μπορούμε να κάνουμε αυτή τη συμπεριφορά προεπιλεγμένη εκτελώντας git config push.recurseSubmodules on-demand.
Συγχώνευση αλλαγών υπομονάδων
Εάν αλλάξουμε μια αναφορά υπομονάδας ταυτόχρονα με κάποιον άλλο, ενδέχεται να αντιμετωπίσουμε κάποια προβλήματα. Δηλαδή, εάν τα ιστορικά των υπομονάδων έχουν αποκλίνει και έχουν υποβληθεί σε διαφορετικούς κλάδους σε ένα υπερ-έργο, μπορεί να χρειαστεί λίγη δουλειά για να το διορθώσουμε.
Εάν μία από τις υποβολές είναι ένας άμεσος πρόγονος της άλλης (μια συγχώνευση ταχυπροώθησης), τότε το Git θα επιλέξει απλά την τελευταίο για τη συγχώνευση και αυτό θα λειτουργήσει μια χαρά.
Ωστόσο, το Git δεν θα επιχειρήσει ούτε καν και μια τετριμμένη συγχώνευση για μας. Εάν οι υποβολές της υπομονάδας αποκλίνουν και πρέπει να συγχωνευτούν, θα πάρουμε κάτι που μοιάζει με αυτό:
$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
9a377d1..eb974f8 master -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
Έτσι, βασικά, αυτό που συνέβη εδώ είναι ότι το Git έχει καταλάβει ότι οι δύο κλάδοι καταγράφουν σημεία στο ιστορικό της υπομονάδας που είναι αποκλίνοντα και πρέπει να συγχωνευθούν. Το εξηγεί ως “δεν βρέθηκε συγχώνευση μετά τις υποβολές” (“merge following commits not found”), που μπερδεύει λιγάκι, αλλά θα εξηγήσουμε για ποιον λόγο σε λίγο.
Για να λύσουμε το πρόβλημα, θα πρέπει να βρούμε σε ποια κατάσταση θα πρέπει να βρίσκεται η υπομονάδα.
Παραδόξως, το Git δεν μας δίνει πραγματικά πολλές πληροφορίες για να μας βοηθήσει εδώ, ούτε καν τους αριθμούς SHA-1 των υποβολών των δύο πλευρών του ιστορικού.
Ευτυχώς, είναι απλό να τη βρούμε.
Αν εκτελέσουμε την git diff, μπορούμε να λάβουμε τους SHA-1 των υποβολών που καταγράφηκαν και στους δύο κλάδους που προσπαθούσαμε να συγχωνεύσουμε.
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
Έτσι, στην περίπτωση αυτή, eb41d76 είναι η υποβολή στην υπομονάδα μας που εμείς είχαμε και c771610 είναι η υποβολή που υπήρχε στο upstream.
Αν πάμε στον κατάλογο υπομονάδων μας, θα πρέπει να είναι ήδη στην eb41d76 καθώς η συγχώνευση δεν τον άγγιξε.
Εάν για οποιονδήποτε λόγο δεν είναι, μπορούμε απλά να δημιουργήσουμε έναν κλάδο που δείχνει σε αυτήν και να μεταβούμε σε αυτόν.
Αυτό που είναι σημαντικό είναι ο SHA-1 της υποβολής από την άλλη πλευρά. Αυτός είναι που θα πρέπει να συγχωνεύσουμε και να επιλύσουμε. Μπορούμε είτε να δοκιμάσουμε τη συγχώνευση με τον SHA-1 απευθείας είτε να δημιουργήσουμε έναν κλάδο για αυτόν και στη συνέχεια να προσπαθήσουμε να τον συγχωνεύσουμε. Προτείνουμε το τελευταίο, έστω και μόνο για να βγάλει ένα καλύτερο μήνυμα συγχώνευσης.
Έτσι, θα πάμε στον κατάλογο υπομονάδων μας, θα δημιουργήσουμε έναν κλάδο με όνομα “try-merge” βασισμένοι σε αυτόν τον δεύτερο αριθμό SHA-1 από την git diff και θα συγχωνεύσουμε χειροκίνητα.
$ cd DbConnector
$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135
$ git branch try-merge c771610
$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.
Έχουμε μια πραγματική σύγκρουση συγχώνευσης εδώ, οπότε αν την επιλύσουμε και την υποβάλλουμε, τότε μπορούμε απλά να ενημερώσουμε το κύριο έργο με το αποτέλεσμα.
$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes
$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
-Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)
$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
-
Επιλύουμε τη σύγκρουση
-
Επιστρέφουμε στον κατάλογο του κύριου έργου
-
Ελέγχουμε τους SHA-1 ξανά
-
Επιλύουμε τη συγκρουόμενη καταχώρηση της υπομονάδας
-
Υποβάλλουμε τη συγχώνευσή μας
Μπορεί να φαίνεται λίγο μπέρδεμα, αλλά δεν είναι πραγματικά πολύ δύσκολο.
Είναι ενδιαφέρον ότι υπάρχει μια άλλη περίπτωση που μπορεί να χειριστεί το Git. Εάν υπάρχει υποβολή συγχώνευσης στον κατάλογο υπομονάδων που περιέχει και τις δύο υποβολές στο ιστορικό της, το Git θα μας το προτείνει ως πιθανή λύση. Βλέπει ότι σε κάποιο σημείο του έργου της υπομονάδας κάποιος συνένωσε κλάδους που περιέχουν αυτές τις δύο υποβολές, οπότε θεωρεί ότι ίσως θέλουμε αυτήν τη συγχώνευση.
Αυτός είναι ο λόγος για τον οποίο το μήνυμα λάθους από πριν ήταν “δεν βρέθηκε συγχώνευση μετά τις υποβολές”, επειδή δεν μπορούσε να κάνει αυτό. Δημιουργεί μπέρδεμα διότι ποιος θα περίμενε να προσπαθήσει να το κάνει κάτι τέτοιο;
Εάν διαπιστώσει ότι υπάρχει μία αποδεκτή υποβολή συγχώνευσης, θα δούμε κάτι σαν αυτό:
$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:
git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"
which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
Αυτό που προτείνει να κάνουμε είναι να ενημερώσουμε το ευρετήριο σαν να είχαμε εκτελέσει git add (που καθαρίζει τη σύγκρουση) και κατόπιν να υποβάλουμε.
Μάλλον δεν πρέπει να το κάνουμε αυτό όμως.
Μπορούμε το ίδιο εύκολα να μεταβούμε στον κατάλογο των υπομονάδων, να δούμε ποια είναι η διαφορά, να ταχυπροωθήσουμε αυτήν την υποβολή, να τη δοκιμάσουμε σωστά και στη συνέχεια να την υποβάλλουμε.
$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward
$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forward to a common submodule child'
Αυτό επιτυγχάνει το ίδιο πράγμα, αλλά τουλάχιστον με αυτόν τον τρόπο μπορούμε να επαληθεύσουμε ότι λειτουργεί και έχουμε τον κώδικα στον κατάλογο υπομονάδων μας όταν τελειώσουμε.
Συμβουλές για υπομονάδες
Υπάρχουν μερικά πράγματα που μπορούμε να κάνουμε, για να κάνουμε την εργασία με τις υπομονάδες λίγο πιο εύκολη.
Submodule Foreach
Υπάρχει μια εντολή submodule foreach για να εκτελέσουμε μία οποιαδήποτε εντολή σε κάθε υπομονάδα.
Αυτό μπορεί να είναι πραγματικά χρήσιμο εάν έχουμε πολλές υπομονάδες στο ίδιο έργο.
Για παράδειγμα, ας υποθέσουμε ότι θέλουμε να ξεκινήσουμε μια νέα λειτουργία ή να διορθώσουμε ένα σφάλμα και έχουμε τρέχουσα εργασία σε διάφορες υπομονάδες. Μπορούμε εύκολα να αποθηκεύσουμε όλη τη δουλειά σε όλες τις υπομονάδες μας.
$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable
Στη συνέχεια, μπορούμε να δημιουργήσουμε έναν νέο κλάδο και να μεταβούμε σε αυτόν σε όλες τις υπομονάδες μας.
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
Αυτή είναι η βασική ιδέα. Κάτι πραγματικά χρήσιμο που μπορούμε να κάνουμε είναι να παράγουμε μια ωραία ενοποιημένη diff από αυτό που αλλάζει στο κύριο έργο μας και όλα τα υποέργα μας.
$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)
commit_pager_choice();
+ url = url_decode(url_orig);
+
/* build alias_argv */
alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
return url_decode_internal(&url, len, NULL, &out, 0);
}
+char *url_decode(const char *url)
+{
+ return url_decode_mem(url, strlen(url));
+}
+
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
Εδώ μπορούμε να δούμε ότι ορίζουμε μια λειτουργία σε μία υπομονάδα και την καλούμε στο κύριο έργο. Αυτό είναι προφανώς ένα απλοποιημένο παράδειγμα, αλλά μας δίνει μια ιδέα για το πώς μπορεί να είναι χρήσιμο.
Χρήσιμα ψευδώνυμα
Μπορεί να θέλουμε να ορίσουμε ορισμένα ψευδώνυμα (aliases) για ορισμένες από αυτές τις εντολές, καθώς μπορεί να είναι αρκετά μακροσκελείς και για τις περισσότερες από αυτές δεν μπορούμε να ορίσουμε επιλογές διαμόρφωσης για να τις καταστήσουμε προεπιλεγμένες. Καλύψαμε τη δημιουργία των ψευδωνύμων του Git στην ενότητα Συντομεύεσεις στο Git, αλλά εδώ είναι ένα παράδειγμα του τι μπορεί να θέλουμε να ρυθμίσουμε εάν σκοπεύουμε να δουλεύουμε συχνά με υπομονάδες στο Git.
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
Με αυτό τον τρόπο μπορούμε απλά να εκτελέσουμε την git supdate όταν θέλουμε να ενημερώσουμε τις υπομονάδες μας ή την git spush για να ωθήσουμε τον έλεγχο εξαρτήσεων των υπομονάδων.
Προβλήματα με τις υπομονάδες
Η χρήση υπομονάδων έχει, κι αυτή, τα προβλήματά της.
Εναλλαγή κλάδων
Για παράδειγμα, η εναλλαγή κλάδων που περιέχουν υπομονάδες μπορεί επίσης να είναι δύσκολη με εκδόσεις του Git παλιότερες 2.13 Εάν δημιουργήσουμε έναν νέο κλάδο, προσθέσουμε σ' αυτόν μία υπομονάδα και στη συνέχεια μεταβούμε σε έναν κλάδο χωρίς αυτήν την υπομονάδα, εξακολουθούμε να έχουμε τον κατάλογο υπομονάδων ως έναν μη-παρακολουθούμενο κατάλογο:
$ git --version
git version 2.12.2
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
CryptoLibrary/
nothing added to commit but untracked files present (use "git add" to track)
Η κατάργηση του καταλόγου δεν είναι δύσκολη, αλλά μπορεί να μας μπερδέψει λίγο η παρουσία του.
Αν τον διαγράψουμε και μετά επιστρέψουμε στον κλάδο που έχει αυτήν την υπομονάδα, θα χρειαστεί να εκτελέσουμε την submodule update --init για να τον επαναοικίσουμε (repopulate).
$ git clean -ffdx
Removing CryptoLibrary/
$ git checkout add-crypto
Switched to branch 'add-crypto'
$ ls CryptoLibrary/
$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'
$ ls CryptoLibrary/
Makefile includes scripts src
Και πάλι, δεν είναι πραγματικά πολύ δύσκολο, αλλά μπορεί να μας μπερδέψει λίγο.
Νέες εκδοσείς Git (Git >= 2.13) απλοποιούν όλα αυτά προσθέτοντας την επιλογή --recurse-submodules στην εντολή git checkout, που αναλαμβάνει να τοποθετήσει τις υπομονάδες στο σωστή κατάσταση για τον κλάδο στον οποίο αλλάζουμε.
$ git --version
git version 2.13.3
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
Χρησιμοποιώντας την επιλογή --recurse-submodules στην git checkout μπορεί επίσης να φανεί χρήσιμη όταν δουλεύουμε σε μερικούς κλάδους στο υπερ-έργο, όπου ο κάθε ένας έχει την υπομονάδα να δείχνει σε διαφορετικές υποβολές.
Πράγματι, αν αλλάξουμε κλάδους όπου η υπομονάδα βρίσκεται σε διαφορετικές υποβολές, όταν εκτελέσουμε git status η υπομονάδα θα δείχνει σαν “αλλαγμενη” (“modified”), και θα υποδεικνύει “νέες υποβολές”.
Αυτό γίνεται επειδή η κατάσταση της υπομονάδας από προεπιλογή δεν μεταφερέται μεταξύ αλλαγών σε κλάδους.
Αυτό είναι πραγματικά πολύ μπέρδεμα, οπότε είναι καλή ιδέα πάντα να τρέχουμε git checkout --recurse-submodules όταν το έργο μας έχει υπομονάδες.
Για παλαιότερες εκδόσεις του Git που δεν έχουν την επιλογή --recurse-submodules, μετά την ενημέρωση μπορούμε να χρησιμοποιήσουμε την εντολή git submodule update --init --recursive για να βάλουμε τις υπομονάδες στην σωστή κατάσταση.
Ευτυχώς, μπορούμε να πούμε στο Git(>=2.14) να χρησιμοποιεί πάντα την επιλογή --recurse-submodules ορίζοντας την επιλογή ρύθμισης submodule.recurse: git config submodule.recurse true.
Όπως σημειώθηκε παραπάνω, αυτό θα κάνει το Git να ανατρέξει αναδρομικά σε υπομονάδες για κάθε εντολή που έχει την επιλογή --recurse-submodules (εκτός της git clone).
Αλλάζοντας από υποκαταλόγους σε υπομονάδες
Ένα άλλο πρόβλημα, που αντιμετωπίζουν πολλοί, εμπλέκει τη μετατροπή υποκαταλόγων σε υπομονάδες.
Αν παρακολουθούμε αρχεία στο έργο μας και θέλουμε να τα μετακινήσουμε σε κάποια υπομονάδα, πρέπει να είμαστε προσεκτικοί αλλιώς το Git θα μας θυμώσει.
Ας υποθέσουμε ότι έχουμε αρχεία σε έναν υποκατάλογο του έργου μας και θέλουμε να τον μετατρέψουμε σε υπομονάδα.
Αν διαγράψουμε τον υποκατάλογο και στη συνέχεια εκτελέσουμε το submodule add, το Git μάς μαλώνει:
$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index
Πρέπει πρώτα να αφαιρέσουμε τον κατάλογο CryptoLibrary από το στάδιο καταχώρισης.
Κατόπιν μπορούμε να προσθέσουμε την υπομονάδα:
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Τώρα ας υποθέσουμε ότι το κάνουμε σε έναν κλάδο. Αν προσπαθήσουμε να μεταβούμε σε κλάδο στον οποίο αυτά τα αρχεία βρίσκονται ακόμα στο πραγματικό δέντρο και όχι σε μία υπομονάδα, λαμβάνουμε αυτό το σφάλμα:
$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting
Μπορούμε να επιβάλουμε τη μετατροπή με την checkout -f, αλλά πρέπει να είμαστε προσεκτικοί να μην υπάρχουν μη αποθηκευμένες αλλαγές εκεί διότι θα μπορούσαν να αντικατασταθούν με αυτήν την εντολή.
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Στη συνέχεια, όταν επιστρέφουμε, παίρνουμε έναν κενό κατάλογο CryptoLibrary για κάποιο λόγο και η git submodule update μπορεί να μην το διορθώσει.
Ενδέχεται να χρειαστεί να μεταβούμε στον κατάλογο υπομονάδων μας και να εκτελέσουμε μία git checkout . για να ξαναπάρουμε όλα τα αρχεία μας.
Θα μπορούσαμε να το κάνουμε αυτό με σε ένα script με submodule foreach, ώστε να εκτελεστεί για πολλές υπομονάδες.
Είναι σημαντικό να σημειώσουμε ότι οι υπομονάδες στις νεότερες εκδόσεις του Git διατηρούν όλα τα δεδομένα Git στον κατάλογο .git του κορυφαίου έργου, έτσι αντίθετα από πολύ παλιότερες εκδόσεις, η καταστροφή ενός καταλόγου υπομονάδων, δεν θα απωλέσει καμία υποβολή ή κλάδο που είχαμε.
Με αυτά τα εργαλεία, οι υπομονάδες μπορούν να είναι μια αρκετά απλή και αποτελεσματική μέθοδος για την ταυτόχρονη ανάπτυξη σε πολλά σχετιζόμενα αλλά ξεχωριστά έργα.