Git
Chapters ▾ 2nd Edition

9.1 Το Git και άλλα συστήματα - Το Git ως πελάτης

Ο κόσμος δεν είναι τέλειος. Συνήθως, δεν μπορούμε να μεταφέρουμε άμεσα κάθε έργο με το οποίο ερχόμαστε σε επαφή στο Git. Μερικές φορές είμαστε κολλημένοι σε ένα έργο που χρησιμοποιεί ένα άλλο VCS και επιθυμούμε να είναι το Git. Θα αφιερώσουμε το πρώτο μέρος αυτού του κεφαλαίου για να μάθουμε τους τρόπους χρήσης του Git ως πελάτη όταν το έργο στο οποίο εργαζόμαστε φιλοξενείται σε διαφορετικό σύστημα.

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

Το Git ως πελάτης

Το Git προσφέρει μια τόσο όμορφη εμπειρία για τους προγραμματιστές που πολλοί έχουν βρει έναν τρόπο να το χρησιμοποιούν στον σταθμό εργασίας τους ακόμα κι αν η υπόλοιπη ομάδα τους χρησιμοποιεί ένα εντελώς διαφορετικό VCS. Τέτοιου είδους προσαρμογείς, ονομάζονται “γέφυρες” και υπάρχουν μερικοί διαθέσιμοι. Εδώ θα καλύψουμε εκείνους που είναι το πιθανότερο να συναντήσουμε στον έξω κόσμο.

Git and Subversion

Ένα μεγάλο μέρος των έργων ανάπτυξης ανοιχτού κώδικα και ένας μεγάλος αριθμός εταιρικών έργων χρησιμοποιεί το Subversion για τη διαχείριση του πηγαίου κώδικα. Το Subversion υπάρχει εδώ και περισσότερα από 10 χρόνια και για το μεγαλύτερο μέρος της εποχής εκείνης υπήρξε η de facto επιλογή ενός VCS για έργα ανοιχτού κώδικα. Είναι επίσης πολύ παρόμοια με πολλούς τρόπους με το CVS, το οποίο ήταν ο μεγάλος παίκτης του ελέγχου πηγαίου κώδικα πριν από αυτό.

Ένα από τα εξαιρετικά χαρακτηριστικά του Git είναι μια αμφίδρομη γέφυρα με το Subversion που ονομάζεται git svn. Αυτό το εργαλείο μάς επιτρέπει να χρησιμοποιούμε το Git ως έγκυρο πελάτη σε έναν διακομιστή Subversion, ώστε να μπορούμε να χρησιμοποιήσουμε όλες τις τοπικές λειτουργίες του Git και στη συνέχεια να ωθήσουμε σε έναν διακομιστή Subversion σαν να χρησιμοποιούσαμε το Subversion τοπικά. Αυτό σημαίνει ότι μπορούμε να κάνουμε τοπικά διακλάδωση και συγχώνευση, να χρησιμοποιήσουμε το στάδιο καταχώρισης, να χρησιμοποιήσουμε την αλλαγή βάσης και την ανθολόγηση κ.ο.κ., ενώ οι συνεργάτες μας συνεχίζουν να εργάζονται με τους σκοτεινούς και αρχαίους τρόπους τους. Είναι ένας καλός τρόπος να περάσουμε αθόρυβα το Git στο εταιρικό περιβάλλον και να βοηθήσουμε τους συνεργάτες μας να γίνουν πιο αποδοτικοί ενώ εμείς ασχολούμαστε να ασκούμε παρασκηνιακές πιέσεις ώστε να αλλάξουμε τις υποδομές ώστε να υποστηρίζουν το Git πλήρως. Η γέφυρα Subversion είναι το ναρκωτικό-προθάλαμος στον κόσμο του DVCS.

git svn

Η βασική εντολή στο Git για όλες τις εντολές γεφύρωσης με το Subversion είναι η git svn. Χρειάζονται αρκετές υποεντολές, επομένως θα δείξουμε τις πιο κοινές ενώ βλέπουμε και μερικές απλές ροές εργασίας.

Είναι σημαντικό να σημειώσουμε ότι όταν χρησιμοποιούμε την git svn, αλληλεπιδράμε με το Subversion, το οποίο είναι ένα σύστημα που λειτουργεί πολύ διαφορετικά από το Git. Παρόλο που μπορούμε να κάνουμε τοπικά διακλαδώσεις και συγχωνεύσεις, είναι γενικά καλύτερα να διατηρήσουμε το ιστορικό μας κατά το δυνατόν γραμμικό, αλλάζοντας τη βάση του έργου μας και αποφεύγοντας να κάνουμε πράγματα όπως να αλληλεπιδρούμε ταυτόχρονα με ένα απομακρυσμένο αποθετήριο Git.

Δεν πρέπει να ξαναγράφουμε το ιστορικό μας και μετά να ωθούμε, ούτε να ωθούμε παράλληλα σε κάποιο αποθετήριο Git για να συνεργαζόμαστε με συνεργάτες προγραμματιστές σε Git. Το Subversion μπορεί να έχει μόνον ένα γραμμικό ιστορικό και το να το μπερδέψουμε είναι πολύ εύκολο. Αν εργαζόμαστε μέσα σε μια ομάδα και κάποιοι χρησιμοποιούν το SVN και άλλοι χρησιμοποιούν το Git, πρέπει να βεβαιωθούμε ότι όλοι χρησιμοποιούν τον διακομιστή SVN για να συνεργαστούν —αυτό θα κάνει τη ζωή μας ευκολότερη.

Εγκατάσταση

Για να επιδείξουμε αυτήν τη λειτουργικότητα, χρειαζόμαστε ένα τυπικό αποθετήριο SVN στο οποίο έχουμε πρόσβαση εγγραφής. Για να αντιγράψουμε αυτά τα παραδείγματα, θα πρέπει να δημιουργήσουμε ένα αντίγραφο της δοκιμαστικής αποθήκης. Για να γίνει αυτό εύκολα, μπορούμε να χρησιμοποιήσουμε ένα εργαλείο που ονομάζεται svnsync που συνοδεύει το Subversion. Για αυτές τις δοκιμές δημιουργήσαμε ένα νέο αποθετήριο Subversion στο Google Code το οποίο ήταν ένα μερικό αντίγραφο του έργου protobuf, το οποίο είναι ένα εργαλείο που κωδικοποιεί δομημένα δεδομένα για μετάδοση στο δίκτυο.

Πρέπει πρώτα να δημιουργήσουμε ένα νέο τοπικό αποθετήριο Subversion:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

Στη συνέχεια, επιτρέπουμε σε όλους τους χρήστες να αλλάξουν revprops —ο εύκολος τρόπος είναι να προσθέσουμε ένα σενάριο pre-revprop-change που πάντα τερματίζει με 0:

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

Τώρα μπορούμε να συγχρονίσουμε αυτό το έργο με το τοπικό μας μηχάνημα καλώντας την svnsync init με τα αποθετήρια από και προς.

$ svnsync init file:///tmp/test-svn \
  http://progit-example.googlecode.com/svn/

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

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

Παρόλο που η λειτουργία αυτή μπορεί να διαρκέσει μόνο λίγα λεπτά, αν προσπαθήσουμε να αντιγράψουμε τον αρχικό αποθετήριο σε ένα άλλο απομακρυσμένο αντί για ένα τοπικό αποθετήριο, η διαδικασία θα διαρκέσει περίπου μία ώρα, παρότι υπάρχουν λιγότερες από 100 υποβολές. Το Subversion πρέπει να κλωνοποιεί μια αναθεώρηση κάθε φορά και στη συνέχεια να την ωθεί ξανά σε ένα άλλο αποθετήριο -είναι γελοία αναποτελεσματικό, αλλά είναι ο μόνος εύκολος τρόπος για να το κάνουμε.

Ξεκίνημα

Τώρα που έχουμε ένα αποθετήριο Subversion στο οποίο έχουμε πρόσβαση εγγραφής, μπορούμε να περάσουμε από μια τυπική ροή εργασίας. Θα ξεκινήσουμε με την εντολή git svn clone, η οποία εισάγει ένα ολόκληρο αποθετήριο Subversion σε ένα τοπικό αποθετήριο Git. Ας θυμηθούμε ότι αν εισάγουμε από ένα πραγματικό αποθετήριο Subversion, θα πρέπει να αντικαταστήσουμε εδώ την file:///tmp/test-svn που υπάρχει εδώ με τη διεύθυνση URL του αποθετηρίου Subversion:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

Η παραπάνω εκτελεί το ισοδύναμο δύο εντολών —git svn init ακολουθούμενη από την git svn fetch— στη διεύθυνση URL που παρέχουμε. Αυτό ίσως διαρκέσει λίγο. Το δοκιμαστικό έργο έχει μόνο περίπου 75 υποβολές και η βάση του κώδικα δεν είναι τόσο μεγάλη, αλλά το Git πρέπει να ελέγχει κάθε έκδοση μία μία και να την υποβάλει ξεχωριστά. Για ένα έργο με εκατοντάδες ή χιλιάδες υποβολές, αυτό μπορεί να πάρει κυριολεκτικά ώρες ή ακόμα και ημέρες για να ολοκληρωθεί.

Το τμήμα The -T trunk -b branches -t tags λέει στο Git ότι αυτό το αποθετήριο Subversion ακολουθεί τις βασικές συμβάσεις διακλάδωσης και σήμανσης με ετικέτες. Εάν ονομάσουμε τον κορμό, τους κλάδους ή τις ετικέτες διαφορετικά, μπορούμε να αλλάξουμε αυτές τις επιλογές. Επειδή αυτό είναι τόσο συνηθισμένο, μπορούμε να αντικαταστήσουμε ολόκληρο αυτό το τμήμα με -s, που σημαίνει τυποποιημένη (standard) διάταξη και υπονοεί όλες αυτές τις επιλογές. Η ακόλουθη εντολή είναι ισοδύναμη:

$ git svn clone file:///tmp/test-svn -s

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

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

Ας σημειωθεί πώς το εργαλείο διαχειρίζεται τις ετικέτες Subversion ως απομακρυσμένες ref. Ας ρίξουμε μια πιο προσεκτική ματιά στην εντολή διοχέτευσης show-ref του Git :

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

Το Git δεν το κάνει αυτό όταν κλωνοποιεί από έναν διακομιστή Git· παρακάτω φαίνεται με τι μοιάζει ένα αποθετήριο με ετικέτες αμριβώς μετά την κλωνοποίηση:

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

Το Git ανακτά τις ετικέτες κατευθείαν στο φάκελο refs/tags, αντί να τις αντιμετωπίζει ως απομακρυσμένους κλάδους.

Υποβολή στο Subversion

Τώρα που έχουμε ένα αποθετήριο στο οποίο εργαζόμαστε, μπορούμε να κάνουμε κάποια εργασία στο έργο και να ωθήσουμε τις υποβολές μας προς τα πάνω, ουσιαστικά χρησιμοποιώντας το Git ως πελάτη SVN. Εάν επεξεργαστούμε ένα από τα αρχεία και το υποβάλουμε, έχουμε μια υποβολή που υπάρχει στο Git τοπικά και δεν υπάρχει στον διακομιστή Subversion:

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

Στη συνέχεια, πρέπει να ωθήσουμε την αλλαγή μας προς τα πάνω. Αυτό αλλάζει τον τρόπο με τον οποίο εργαζόμαστε με το Subversion —μπορούμε να εκτελέσουμε αρκετές υποβολές τοπικά και στη συνέχεια να τις ωθήσουμε ταυτόχρονα στον διακομιστή Subversion. Για να ωθήσουμε σε έναν διακομιστή Subversion, εκτελούμε την εντολή git svn dcommit:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Αυτό λαμβάνει όλες τις υποβολές που κάναμε πάνω από τον κώδικα του διακομιστή Subversion, πραγματοποιεί μια υποβολή Subversion για καθεμία και μετά ξαναγράφει την τοπική μας υποβολή Git για να συμπεριλάβουμε ένα μοναδικό αναγνωριστικό. Αυτό είναι σημαντικό επειδή σημαίνει ότι όλα τα αθροίσματα ελέγχου SHA-1 για τις υποβολές μας αλλάζουν. Εν μέρει για αυτόν τον λόγο, η εργασία με απομακρυσμένες εκδόσεις του έργου μας σε Git ταυτόχρονα με εργασία σε έναν διακομιστή Subversion δεν είναι καλή ιδέα. Αν κοιτάξουμε την τελευταία υποβολή, μπορούμε να δούμε το νέο git-svn-id που προστέθηκε:

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

Παρατηρούμε ότι το άθροισμα ελέγχου SHA-1 που ξεκίνησε αρχικά με το 4af61fd όταν υποβάλαμε τώρα αρχίζει με` 95e0222`. Αν θέλουμε να ωθήσουμε τόσο σε έναν διακομιστή Git όσο και σε έναν διακομιστή Subversion, πρέπει πρώτα να ωθήσουμε (dcommit) στον διακομιστή Subversion, επειδή η ενέργεια αυτή αλλάζει τα δεδομένα της υποβολής μας.

Έλξη νέων αλλαγών

Εάν συνεργαζόμαστε με άλλους προγραμματιστές, τότε σε κάποιο σημείο ένας από εμάς θα ωθήσει και μετά κάποιος άλλος θα προσπαθήσει να ωθήσει μια αλλαγή που έρχεται σε σύγκρουση με την πρώτη. Η αλλαγή αυτή θα απορριφθεί μέχρι να συγχωνεύσουμε την εργασία του. Στην git svn, μοιάζει με αυτό:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Για να επιλύσουμε αυτήν την κατάσταση, μπορούμε να εκτελέσουμε την git svn rebase, η οποία καταργεί οποιεσδήποτε αλλαγές στον διακομιστή δεν έχουμε ακόμα και επαντοποθετεί οποιαδήποτε εργασία έχουμε πάνω από αυτό που βρίσκεται στον server:

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Τώρα, όλη η δουλειά μας είναι πάνω από αυτό που βρίσκεται στον διακομιστή Subversion, έτσι ώστε να μπορούμε να τρέξουμε dcommit με επιτυχία:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Ας σημειωθεί ότι αντίθετα με το Git, το οποίο απαιτεί να συγχωνεύσουμε την εργασία που έχει γίνει upstream και δεν έχουμε ακόμα τοπικά προτού μπορέσουμε να ωθήσουμε, το git svn μας επιβάλει να το κάνουμε αυτό μόνο αν οι αλλαγές συγκρούονται (όπως ακριβώς λειτουργεί το Subversion). Αν κάποιος άλλος ωθήσει μια αλλαγή σε ένα αρχείο και στη συνέχεια ωθήσουμε μια αλλαγή σε άλλο αρχείο, το dcommit μας θα λειτουργήσει μια χαρά:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...

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

Θα πρέπει επίσης να εκτελέσουμε αυτήν την εντολή για να έλξουμε αλλαγές από τον διακομιστή Subversion, ακόμη και αν δεν είμαστε έτοιμοι να υποβάλουμε. Μπορούμε να εκτελέσουμε το git svn fetch για να τραβήξουμε τα νέα δεδομένα, αλλά η git svn rebase κάνει την ανάκτηση και στη συνέχεια ενημερώνει τις τοπικές υποβολές μας.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

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

Ζητήματα διακλάδωσης στο Git

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

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

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Η εκτέλεση της dcommit σε έναν κλάδο με συγχωνευμένο ιστορικό λειτουργεί καλά, εκτός από το ότι όταν εξετάζουμε το ιστορικό του έργου Git, δεν έχει ξαναγράψει καμία από τις υποβολές που κάναμε στον κλάδο experiment —αντίθετα όλες αυτές οι αλλαγές εμφανίζονται στην έκδοση SVN της μοναδικής υποβολής συγχώνευσης.

Όταν κάποιος άλλος κλωνοποιεί αυτό το έργο, το μόνο που βλέπει είναι ότι η συγχώνευση υποβάλλεται με όλη τη δουλειά που στριμώχνεται σε αυτό, σαν να είχαμε τρέξει git merge --squash· δεν βλέπουν τα δεδομένα υποβολής σχετικά με το από πού προήλθε ή πότε υποβλήθηκε.

Διακλαδώσεις στο Subversion

Η διακλαδώσεις στο Subversion δεν είναι ίδιες με τις διακλαδώσεις στο Git· αν μπορούμε να αποφύγουμε να τις χρησιμοποιούμε πολύ, αυτό είναι μάλλον το καλύτερο. Ωστόσο, μπορούμε να δημιουργήσουμε κλάδους και τους υποβάλουμε στο Subversion χρησιμοποιώντας την git svn.

Δημιουργία νέου κλάδου στο SVN

Για να δημιουργήσουμε έναν νέο κλάδο στο Subversion, εκτελούμε την git svn branch [όνομα_κλάδου]:

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

Αυτό ισοδυναμεί με την εντολή svn copy trunk branches/opera στο Subversion και λειτουργεί στον διακομιστή Subversion. Είναι σημαντικό να σημειωθεί ότι αυτή η εντολή δεν μας μεταφέρει σε αυτόν τον κλάδο· εάν υποβάλουμε σε αυτό το σημείο, αυτή η υποβολή θα μεταβεί στον trunk του διακομιστή, όχι στον όπερα.

Μετάβαση μεταξύ ενεργών κλάδων

Το Git υπολογίζει σε ποιον κλάδο πηγαίνουν τα dcommit μας αναζητώντας την άκρη όλων των κλάδων Subversion στο ιστορικό μας —θα πρέπει να έχουμε μόνο ένα και θα πρέπει να είναι το τελευταίο με ένα git-svn-id στο τρέχον ιστορικό του κλάδου μας.

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

$ git branch opera remotes/origin/opera

Τώρα, εάν θέλουμε να συγχωνεύσουμε τον κλάδο μας opera σε trunk (τον κύριο κλάδο μας), μπορούμε να το κάνουμε με μια κανονική git merge. Αλλά πρέπει να δώσουμε ένα περιγραφικό μήνυμα υποβολής (με -m), αλλιώς η συγχώνευση θα λέει “Merge branch opera” αντί για κάτι χρήσιμο.

Ας θυμηθούμε ότι παρόλο που χρησιμοποιούμε την git merge για να κάνουμε αυτήν τη λειτουργία και η συγχώνευση πιθανόν θα είναι πολύ πιο εύκολη από ό,τι θα ήταν στο Subversion (επειδή το Git θα ανιχνεύσει αυτόματα την κατάλληλη βάση συγχώνευσης για μας), αυτή δεν είναι μία κανονική υποβολή συγχώνευσης του Git. Πρέπει να ωθήσουμε αυτά τα δεδομένα πίσω σε έναν διακομιστή Subversion που δεν μπορεί να χειριστεί μια υποβολή που παρακολουθεί περισσότερους από έναν γονείς· οπότε, αφού τα ωθήσουμε, θα μοιάζει με μια μία μοναδική υποβολή που στρίμωξε όλη τη δουλειά ενός άλλου κλάδου κάτω από μια και μοναδική υποβολή. Αφού συγχωνεύσουμε έναν κλάδο σε κάποιον άλλο, δεν μπορούμε εύκολα να επιστρέψουμε και να συνεχίσουμε να εργαζόμαστε σε αυτόν τον κλάδο, όπως συνήθως μπορούμε στο Git. Η εντολή dcommit που τρέχουμε διαγράφει κάθε πληροφορία που λέει σε ποιον κλάδο συγχωνεύθηκε, έτσι οι μεταγενέστεροι υπολογισμοί βάσης συγχώνευσης θα είναι λανθασμένοι —η dcommit κάνει το αποτέλεσμα git merge να μοιάζει σαν να τρέξαμε git merge --squash. Δυστυχώς, δεν υπάρχει κάποιος καλός τρόπος για να αποφύγουμε αυτήν την κατάσταση —το Subversion δεν μπορεί να αποθηκεύσει αυτές τις πληροφορίες, έτσι θα είμαστε πάντα πάντα περιορισμένοι από τους περιορισμούς του, εφόσον το χρησιμοποιούμε στον διακομιστή μας. Για να αποφύγουμε τα προβλήματα, πρέπει να διαγράψουμε τον τοπικό κλάδο (στην περίπτωση αυτήν τον opera) αφού τον συγχωνεύσουμε στον κορμό.

Εντολές Subversion

Το σύνολο εργαλείων git svn παρέχει πολλές εντολές για να διευκολύνει τη μετάβαση στο Git παρέχοντας κάποια λειτουργικότητα παρόμοια με αυτήν που είχαμε στο Subversion. Ακολουθούν μερικές εντολές που μας δίνουν αυτά που έδινε το Subversion.

Μορφή ιστορικού στο SVN

Αν είμαστε συνηθισμένοι στο Subversion και θέλουμε να δούμε το ιστορικό μας σε στυλ SVN, μπορούμε να εκτελέσουμε την git svn log για να δούμε το ιστορικό των εργασιών μας σε μορφή SVN:

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog

Θα πρέπει να γνωρίζουμε δύο σημαντικά πράγματα για την git svn log. Πρώτον, λειτουργεί εκτός σύνδεσης, σε αντίθεση με την πραγματική εντολή svn log, η οποία ζητάει από τον διακομιστή Subversion τα δεδομένα. Δεύτερον, δείχνει μόνο υποβολές που έχουν υποβληθεί στον διακομιστή Subversion. Τοπικές υποβολές του Git που δεν τις έχουμε κάνει dcommit ακόμα δεν εμφανίζονται· ούτε οι υποβολές που έχουν κάνει άλλοι στον διακομιστή Subversion στο μεταξύ. Είναι περισσότερο σαν την τελευταία γνωστή κατάσταση των υποβολων στον διακομιστή Subversion.

Επισημειώσεις στο SVN

Καθώς η εντολή git svn log προσομοιώνει την εντολή svn log εκτός σύνδεσης, μπορούμε να πάρουμε το ισοδύναμο της svn annotate εκτελώντας την git svn blame [αρχείο]. Η έξοδος μοιάζει ως εξής:

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

Και πάλι, δεν δείχνει υποβολές που κάναμε τοπικά στο Git ή που έχουν ωθηθεί στο Subversion εν τω μεταξύ.

Πληροφορίες διακομιστή SVN

Μπορούμε επίσης να αποκτήσουμε το ίδιο είδος πληροφορίας που μας δίνει η svn info, τρέχοντας την git svn info:

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

Αυτό είναι σαν τις blame και log στο ότι εκτελείται εκτός σύνδεσης και είναι ενημερωμένο μόνο μέχρι την τελευταία φορά που επικοινωνήσαμε με τον διακομιστή Subversion.

Παράβλεψη των παραβλέψεων του Subversion

Αν κλωνοποιήσουμε ένα αποθετήριο Subversion που έχει τις ιδιότητες svn:ignore που ορίζουμε οπουδήποτε, πιθανότατα θέλουμε να ορίσουμε αντίστοιχα αρχεία .gitignore έτσι ώστε να μην υποβάλουμε κατά λάθος αρχεία που δεν θα έπρεπε. Η git svn έχει δύο εντολές για να βοηθήσει με αυτό το πρόβλημα. Η πρώτη είναι η git svn create-ignore, η οποία δημιουργεί αυτόματα τα αντίστοιχα αρχεία .gitignore για μας, έτσι ώστε η επόμενη υποβολή να τα συμπεριλάβει.

Η δεύτερη εντολή είναι η git svn show-ignore, η οποία εκτυπώνει στην stdout τις γραμμές που πρέπει να βάλουμε σε ένα αρχείο .gitignore ώστε να μπορούμε να ανακατευθύνουμε την έξοδο στο αρχείο εξαιρέσεων του έργου:

$ git svn show-ignore > .git/info/exclude

Με αυτόν τον τρόπο, δεν γεμίζουμε το έργο με αρχεία .gitignore. Αυτή είναι μια καλή επιλογή αν είμαστε ο μόνος χρήστης Git σε μια ομάδα Subversion και οι συνεργάτες μας δεν θέλουν αρχεία .gitignore στο έργο.

Περίληψη Git-SVN

Τα εργαλεία git svn είναι χρήσιμα αν είμαστε κολλημένοι με έναν διακομιστή Subversion ή βρισκόμαστε σε περιβάλλον ανάπτυξης που απαιτεί τη χρήση διακομιστή Subversion. Θα πρέπει να το δούμε σαν σακατεμένο Git αλλιώς θα συνατήσουμε ζητήματα στη μετάφραση από το ένα σύστημα στο άλλο που μπορεί να μπερδέψουν εμάς και τους συνεργάτες μας. Για να αποφύγουμε τα προβλήματα, καλό είναι να ακολουθούμε αυτές τις οδηγίες:

  • Διατηρούμε ένα γραμμικό ιστορικό Git που δεν περιέχει υποβολές συγχώνευσης που έγιναν με την git merge. Κάθε εργασία που κάνουμε έξω από τον κλάδο της κύριας γραμμής την επανατοποθετούμε ξανά σε αυτήν (αλλάζουμε τη βάση της στην κύρια γραμμή)· δεν τη συγχωνεύουμε.

  • Δεν εγκαθιστούμε και συνεργαζόμαστε σε ξεχωριστό διακομιστή Git. Ενδεχομένως έχουμε έναν για να επιταχύνουμε τους κλώνους για νέους προγραμματιστές, αλλά δεν ωθούμε τίποτα σε αυτόν που δεν έχει καταχώρηση git-svn-id. Ίσως ακόμα θελήσουμε να προσθέσουμε ένα άγκιστρο pre-receive που ελέγχει κάθε μήνυμα αποστολής για ένα git-svn-id και απορρίπτει ωθήσεις που περιέχουν υποβολές χωρίς αυτό.

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

Git και Mercurial

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

Τα καλά νέα, εφόσον προτιμάμε τη συμπεριφορά από την πλευρά του πελάτη του Git αλλά εργαζόμαστε με ένα έργο του οποίου ο πηγαίος κώδικας ελέγχεται με το Mercurial, είναι ότι υπάρχει ένας τρόπος να χρησιμοποιήσουμε το Git ως πελάτη για ένα αποθετήριο που φιλοξενείται από Mercurial. Δεδομένου ότι ο τρόπος με τον οποίο το Git μιλάει με αποθετήρια διακομιστών μέσω απομακρυσμένων, δεν πρέπει να αποτελεί έκπληξη ότι αυτή η γέφυρα έχει υλοποιηθεί ως απομακρυσμένος βοηθός. Το όνομα του έργου είναι git-remote-hg και μπορεί να βρεθεί στη https://github.com/felipec/git-remote-hg.

git-remote-hg

Πρώτα, πρέπει να εγκαταστήσουμε το git-remote-hg. Αυτό ουσιαστικά γίνεται αν απλά αντιγράψουμε το αρχείο κάπου στη διαδρομή μας, όπως παρακάτω:

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

… με την προϋπόθεση ότι ο ~/bin είναι στο $PATH μας. η git-remote-hg έχει άλλη μία εξάρτηση: τη βιβλιοθήκη Python του Mercurial. Αν έχουμε εγκατεστημένη την Python, αυτό γίνεται πανεύκολα:

$ pip install mercurial

(Εάν δεν έχουμε εγκαταστήσει την Python, επισκεφτόμαστε τη διεύθυνση https://www.python.org/ για να την κατεβάσουμε και να την εγκαταστήσουμε.)

Το τελευταίο πράγμα που θα χρειαστούμε είναι ο πελάτης του Mercurial. Μεταβαίνουμε στη διεύθυνση http://mercurial.selenic.com/ και τον εγκαταθιστούμε αν δεν το έχουμε ήδη κάνει.

Τώρα είμαστε έτοιμοι να παίξουμε μπάλα. Το μόνο που χρειαζόμαστε είναι ένα αποθετήριο Mercurial στο οποίο να έχουμε δικαίωμα ώθησης. Ευτυχώς, κάθε αποθετήριο Mercurial μπορεί να ενεργήσει με αυτόν τον τρόπο, οπότε θα χρησιμοποιήσουμε μόνο το αποθετήριο hello world που χρησιμοποιούν όλοι για να μάθουν το Mercurial:

$ hg clone http://selenic.com/repo/hello /tmp/hello

Ξεκινώντας

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

Όπως πάντα στο Git, αρχικά κλωνοποιούμε:

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Παρατηρούμε ότι η εργασία με ένα αποθετήριο Mercurial χρησιμοποιεί την τυπική εντολή git clone. Αυτό συμβαίνει επειδή το git-remote-hg λειτουργεί σε αρκετά χαμηλό επίπεδο, χρησιμοποιώντας έναν παρόμοιο μηχανισμό με αυτόν που είναι υλοποιημένο το πρωτόκολλο HTTP/S του Git (με απομακρυσμένους βοηθούς). Δεδομένου ότι τα Git και Mercurial έχουν σχεδιαστεί και τα δύο ώστε κάθε πελάτης να έχει ένα πλήρες αντίγραφο του ιστορικού του αποθετηρίου, αυτή η εντολή δημιουργεί έναν πλήρη κλώνο, συμπεριλαμβανομένου ολόκληρου του ιστορικού του έργου και μάλιστα το κάνει αρκετά γρήγορα.

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

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Το git-remote-h`g προσπαθεί να κάνει το ιδίωμα πιο Git-οειδές, αλλά εσωτερικά διαχειρίζεται την εννοιολογική απεικόνιση μεταξύ δύο ελαφρώς διαφορετικών συστημάτων. Ο κατάλογος `refs/hg είναι εκεί όπου αποθηκεύονται οι πραγματικές απομακρυσμένες αναφορές. Για παράδειγμα, το αρχείο refs/hg/origin/branches/default είναι ένα αρχείο ref του Git που περιέχει τον SHA-1 που αρχίζει με το ac7955c. Έτσι, ο κατάλογος refs/hg είναι σαν ένα ψεύτικο refs/remotes/origin, αλλά έχει την πρόσθετη διάκριση μεταξύ σελιδοδεικτών και κλάδων.

Το αρχείο notes/hg είναι το σημείο εκκίνησης για τον τρόπο με τον οποίο το git-remote-hg απεικονίζει τους αριθμούς SHA-1 των υποβολών Git σε ID συνόλων αλλαγών του Mercurial. Ας το εξερευνήσουμε αυτό λίγο:

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

Επομένως, το refs/notes/hg δείχνει σε ένα δέντρο, το οποίο στη βάση δεδομένων αντικειμένων του Git είναι μια λίστα άλλων αντικειμένων με ονόματα. Το git ls-tree εξάγει τα δικαιώματα πρόσβασης, τον τύπο, το hash αντικειμένου και το όνομα αρχείου για τα στοιχεία μέσα σε ένα δέντρο. Μόλις σκάψουμε σε ένα από τα στοιχεία του δέντρου, διαπιστώνουμε ότι μέσα του υπάρχει ένα blob που ονομάζεται ac9117f (ο αριθμός SHA-1 της υποβολής στην οποία δείχνει ο 'master`), με τα περιεχόμενα 0a04b98 (που είναι το αναγνωριστικό του συνόλου αλλαγών του Mercurial στην κορυφή του κλάδου default.

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

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

$ cp .hgignore .git/info/exclude

Το αρχείο .git/info/exclude λειτουργεί ακριβώς όπως το .gitignore, αλλά δεν περιλαμβάνεται στις υποβολές.

Ροή εργασίας

Ας υποθέσουμε ότι έχουμε κάνει κάποια εργασία και κάποιες υποβολές στον κλάδο master και είμαστε έτοιμοι να τις ωθήσουμε στο απομακρυσμένο αποθετήριο. Εδώ φαίνεται το αποθετήριό μας:

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Ο κλάδος master προηγείται κατά δύο υποβολές του origin/master, αλλά αυτές οι δύο υποβολές υπάρχουν μόνο στο τοπικό μας μηχάνημα. Ας δούμε αν κάποιος άλλος έχει κάνει σημαντικό έργο την ίδια στιγμή:

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Εφόσον χρησιμοποιήσαμε τη σημαία --all, βλέπουμε τις αναφορές “notes” που χρησιμοποιούνται εσωτερικά από το git-remote-hg, αλλά μπορούμε να τις αγνοήσουμε. Τα υπόλοιπα είναι όπως τα αναμέναμε· ο origin/master έχει προχωρήσει κατά μία υποβολή και το ιστορικό μας έχει αποκλίνει τώρα. Σε αντίθεση με τα άλλα συστήματα με τα οποία εργαζόμαστε σε αυτό το κεφάλαιο, το Mercurial είναι ικανό να χειριστεί τις συγχωνεύσεις, οπότε δεν πρόκειται να κάνουμε τίποτα φανταχτερό.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Τέλεια. Εκτελούμε τις δοκιμές και όλα πετυχαίνουν, οπότε είμαστε έτοιμοι να μοιραστούμε το έργο μας με την υπόλοιπη ομάδα:

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

Αυτό ήταν! Εάν ρίξουμε μια ματιά στο αποθετήριο Mercurial, θα δούμε ότι έκανε αυτό που αναμέναμε:

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Το σύνολο των αλλαγών 2 έγινε από το Mercurial και τα σύνολα αλλαγών 3 και 4 έγιναν από το git-remote-hg, με ώθηση των υποβολών που έγιναν με το Git.

Κλάδοι και σελιδοδείκτες

Το Git έχει μόνο ένα είδος κλάδου: μια αναφορά που μετακινείται όταν γίνονται υποβολές. Στο Mercurial, αυτό το είδος μίας αναφοράς ονομάζεται “σελιδοδείκτης” και συμπεριφέρεται με τον ίδιο τρόπο όπως ένας κλάδος Git.

Η αντίληψη του Mercurial περί “κλάδου” είναι πιο βαριά. Ο κλάδος στο οποίο πραγματοποιείται ένα σύνολο αλλαγών καταγράφεται με το σύνολο αλλαγών (changeset), που σημαίνει ότι θα βρίσκεται πάντα στο ιστορικό του αποθετηρίου. Ακολουθεί ένα παράδειγμα υποβολής που έγινε στον κλάδο develop:

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

Παρατηρούμε τη γραμμή που αρχίζει με branch. Το Git δεν μπορεί να αναπαράγει αυτό το πράγμα (και ούτε χρειάζεται, αφού και οι δύο τύποι κλάδων μπορούν να αναπαρασταθούν ως ref του Git), αλλά το git-remote-hg πρέπει να κατανοήσει τη διαφορά, επειδή το Mercurial πρέπει να ξέρει.

Η δημιουργία των σελιδοδεικτών στο Mercurial είναι τόσο εύκολη όσο και η δημιουργία κλάδων στο Git. Από την πλευρά του Git:

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

Αυτό ήταν όλο. Από την πλευρά του Mercurial, μοιάζει με αυτό:

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Παρατηρούμε τη νέα ετικέτα [featureA] στην αναθεώρηση 5. Αυτά λειτουργούν ακριβώς όπως οι κλάδοι Git στην πλευρά του Git, με μια εξαίρεση: δεν μπορούμε να διαγράψουμε έναν σελιδοδείκτη από την πλευρά του Git (αυτός είναι ένας περιορισμός των απομακρυσμένων βοηθών).

Επίσης μπορούμε να εργαστούμε σε έναν κλάδο “βαρέων βαρών” του Mercurial: απλά βάζουμε έναν κλάδο στον ονοματοχώρο branches:

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

Ακολουθεί το πώς μοιάζει από την πλευρά του Mercurial:

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

Το όνομα κλάδου permanent καταγράφηκε με το σύνολο αλλαγών 7.

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

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Οι αλλαγές 8, 9 και 10 έχουν δημιουργηθεί και ανήκουν στον κλάδο permanent, αλλά τα παλιά σύνολα αλλαγών είναι ακόμα εκεί. Αυτό μπορεί να επιφέρει πολλή σύγχυση στους συνεργάτες μας που χρησιμοποιούν Mercurial, οπότε προσπαθούμε να το αποφύγουμε.

Ανακεφαλαίωση Mercurial

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

Git and Perforce

Το Perforce είναι ένα πολύ δημοφιλές σύστημα ελέγχου εκδόσεων σε εταιρικά περιβάλλοντα. Κυκλοφορεί από το 1995, γεγονός που το καθιστά το παλαιότερο σύστημα που καλύπτεται σε αυτό το κεφάλαιο. Ως εκ τούτου, έχει σχεδιαστεί με τους περιορισμούς της εποχής του. Υποθέτει ότι είμαστε πάντα συνδεδεμένοι σε έναν κεντρικό διακομιστή και ότι διατηρείται μόνο μία έκδοση στον τοπικό δίσκο. Βεβαίως, οι λειτουργίες και οι περιορισμοί του είναι κατάλληλα για αρκετά συγκεκριμένα προβλήματα, αλλά υπάρχουν πολλά έργα που χρησιμοποιούν το Perforce στα οποία το Git θα λειτουργούσε πραγματικά καλύτερα.

Υπάρχουν δύο επιλογές αν θέλουμε να συνδυάσουμε τη χρήση του Perforce και του Git. Η πρώτη που θα καλύψουμε είναι η γέφυρα “Git Fusion” από τους κατασκευαστές του Perforce, που μας επιτρέπει να εκθέτουμε υποδέντρα μίας αποθήκης Perforce ως αποθετήρια Git για ανάγνωση και εγγραφή. Το δεύτερο είναι το git-p4, μια γέφυρα από την πλευρά του πελάτη, που μας επιτρέπει να χρησιμοποιήσουμε το Git ως πελάτη του Perforce, χωρίς να απαιτήσουμε νέα διαμόρφωση του διακομιστή Perforce.

Git Fusion

Το Perforce παρέχει ένα προϊόν που ονομάζεται Git Fusion (διαθέσιμο στη http://www.perforce.com/git-fusion), το οποίο συγχρονίζει έναν διακομιστή Perforce με αποθετήρια Git στην πλευρά του διακομιστή.

Εγκατάσταση

Για τα παραδείγματα μας, θα χρησιμοποιήσουμε την πιο εύκολη μέθοδο εγκατάστασης για το Git Fusion, η οποία είναι να κατεβάσουμε μία εικονική μηχανή (virtual machine) που τρέχει το δαίμονα Perforce και το Git Fusion. Μπορούμε να πάρουμε την εικόνα της εικονικής μηχανής από το http://www.perforce.com/downloads/Perforce/20-User, και μόλις ολοκληρωθεί η λήψη, την εισάγουμε το στο αγαπημένο μας λογισμικό εικονικοποίησης (θα χρησιμοποιήσουμε το VirtualBox).

Με την πρώτη εκκίνηση του μηχανήματος, μάς ζητά να προσαρμόσουμε τον κωδικό πρόσβασης για τρεις χρήστες Linux (root, perforce και git) και να δώσουμε ένα όνομα στιγμιότυπου, το οποίο μπορεί να χρησιμοποιηθεί για να διακρίνει αυτήν την εγκατάσταση από άλλους στο ίδιο δίκτυο. Όταν όλα αυτά ολοκληρωθούν, θα δούμε τα εξής:

Η οθόνη εκκίνησης εικονικής μηχανής Git Fusion.
Figure 145. Η οθόνη εκκίνησης εικονικής μηχανής Git Fusion.

Θα πρέπει να λάβουμε υπόψη τη διεύθυνση IP που εμφανίζεται εδώ, θα τη χρησιμοποιήσουμε αργότερα. Στη συνέχεια, θα δημιουργήσουμε έναν χρήστη Perforce. Επιλέγουμε “Login” στο κάτω μέρος και πατάμε Enter (ή SSH στο μηχάνημα) και συνδεόμαστεε ως root. Στη συνέχεια, χρησιμοποιούμε αυτές τις εντολές για να δημιουργήσουμε έναν χρήστη:

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

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

Το επόμενο πράγμα που πρέπει να κάνουμε είναι να πούμε στο Git να μην επαληθεύει τα πιστοποιητικά SSL. Η εικόνα Git Fusion έρχεται με πιστοποιητικό, αλλά είναι για έναν domain που δεν ταιριάζει με τη διεύθυνση IP της εικονικής μηχανής μας, οπότε το Git θα απορρίψει τη σύνδεση HTTPS. Εάν πρόκειται να γίνει μόνιμη εγκατάσταση, συμβουλευόμαστε το εγχειρίδιο του Perforce Git Fusion για να εγκαταστήσουμε ένα διαφορετικό πιστοποιητικό· για τον σκοπό των παραδειγμάτων μας, αυτό αρκεί:

$ export GIT_SSL_NO_VERIFY=true

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

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

Η εικόνα εικονικής μηχανής έρχεται εξοπλισμένη με ένα παράδείγμα έργου που μπορούμε να κλωνοποιήσουμε. Εδώ κλωνοποιούμε μέσα από το HTTPS, με τον χρήστη john που δημιουργήσαμε παραπάνω. Το Git ζητά διαπιστευτήρια για αυτήν τη σύνδεση, αλλά η προσωρινή μνήμη των διαπιστευτηρίων θα μας επιτρέψει να παραλείψουμε αυτό το βήμα για τυχόν μεταγενέστερα αιτήματα.

Διαμόρφωση του Git Fusion

Μόλις εγκαταστήσουμε το Git Fusion, θα χρειαστεί να τροποποιήσουμε τη διαμόρφωση. Αυτό γίνεται πραγματικά πολύ εύκολα χρησιμοποιώντας οποιονδήποτε πελάτη Perforce· απλά απεικονίζουμε τον κατάλογο //.git-fusion στον διακομιστή Perforce στον χώρο εργασίας μας. Η δομή του αρχείου είναι η εξής:

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

Ο κατάλογος objects χρησιμοποιείται εσωτερικά από το Git Fusion για να απεικονίσει αντικείμενα Perforce στο Git και το αντίστροφο, δεν θα χρειαστεί να κάνουμε τίποτα εκεί. Υπάρχει ένα καθολικό αρχείο p4gf_config σε αυτόν τον κατάλογο, καθώς και ένα για κάθε αποθετήριο —αυτά είναι τα αρχεία διαμόρφωσης που καθορίζουν τον τρόπο συμπεριφοράς του Git Fusion. Ας ρίξουμε μια ματιά στο αρχείο στη ρίζα:

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

Δεν θα εξηγήσουμε τι είναι η κάθε σημαία εδώ, αλλά ας σημειωθεί ότι αυτό είναι απλά ένα αρχείο κειμένου INI, όπως αυτά που χρησιμοποιεί το Git για τη διαμόρφωση. Αυτό το αρχείο καθορίζει τις γενικές επιλογές, οι οποίες μπορούν στη συνέχεια να αντικατασταθούν από συγκεκριμένα αρχεία διαμόρφωσης για κάθε αποθετήριο, όπως repos/Talkhouse/p4gf_config. Εάν ανοίξουμε αυτό το αρχείο, θα δούμε μια ενότητα [@repo] με ορισμένες ρυθμίσεις που διαφέρουν από τις καθολικές προεπιλογές. Θα δούμε επίσης τμήματα που μοιάζουν με αυτά:

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

Πρόκειται για μία απεικόνιση μεταξύ κλάδου Perforce και κλάδου Git. Η ενότητα μπορεί να ονομαστεί ό,τι θέλουμε, αρκεί το όνομα να είναι μοναδικό. Η git-branch-name μάς επιτρέπει να μετατρέψουμε μια διαδρομή αποθήκης που θα ήταν δύσχρηστη στο Git σε ένα πιο φιλικό όνομα. Η ρύθμιση view ελέγχει τον τρόπο απεικόνισης των αρχείων Perforce στο αποθετήριο Git χρησιμοποιώντας τη σύνταξη απεικόνισης τυπικής προβολής. Είναι δυνατό να καθοριστούν περισσότερες από μία απεικονίσεις, όπως σε αυτό το παράδειγμα:

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

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

Το τελευταίο αρχείο που θα συζητήσουμε είναι το users/p4gf_usermap, το οποίο απεικονίζει χρήστες του Perforce σε χρήστες του Git και ίσως δεν χρειάζεται καν. Κατά τη μετατροπή από μια σειρά αλλαγών Perforce σε μια υποβολή Git, η προεπιλεγμένη συμπεριφορά του Git Fusion είναι να αναζητήσει τον χρήστη του Perforce και να χρησιμοποιήσει τη διεύθυνση e-mail και το πλήρες όνομα που έχει αποθηκευτεί εκεί για το πεδίο συγγραφέα/υποβάλλοντος στο Git. Κατά την αντίστροφη μετατροπή, η προεπιλογή είναι να αναζητήσουμε τον χρήστη Perforce με τη διεύθυνση e-mail που είναι αποθηκευμένη στο πεδίο συγγραφέα της υποβολής Git και να υποβάλουμε το σύνολο αλλαγών ως αυτός ο χρήστης (με την εφαρμογή των αντίστοιχων δικαιωμάτων). Στις περισσότερες περιπτώσεις, αυτή η συμπεριφορά θα δουλέψει μια χαρά, αλλά ας εξετάσουμε το ακόλουθο αρχείο απεικόνισης:

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

Κάθε γραμμή έχει τη μορφή <χρήστης> <email> '<πλήρες όνομα>" και δημιουργεί μία μόνο απεικόνιση χρήστη. Οι δύο πρώτες γραμμές αντιστοιχίζονται σε δύο διαφορετικές διευθύνσεις e-mail στον ίδιο λογαριασμό χρήστη Perforce. Αυτό είναι χρήσιμο αν έχουμε δημιουργήσει υποβολές Git κάτω από πολλές διαφορετικές διευθύνσεις e-mail (ή να αλλάξαμε διεύθυνση e-mail), αλλά θέλουμε να αντιστοιχιστούν στον ίδιο χρήστη Perforce. Κατά τη δημιουργία μιας υποβολής Git από ένα σύνολο αλλαγών Perforce, η πρώτη γραμμή που ταιριάζει με τον χρήστη Perforce χρησιμοποιείται για τις πληροφορίες συγγραφέων Git.

Οι τελευταίες δύο γραμμές καλύπτουν τα πραγματικά ονόματα και τις διευθύνσεις e-mail του Bob και του Joe από τις υποβολές του Git που δημιουργούνται. Αυτό είναι ωραίο αν θέλουμε να ανοίξουμε ένα εσωτερικό έργο, αλλά δεν θέλουμε να δημοσιεύσουμε τον κατάλογο των υπαλλήλων μας σε ολόκληρο τον κόσμο. Ας σημειωθεί ότι οι διευθύνσεις e-mail και τα πλήρη ονόματα θα πρέπει να είναι μοναδικά, εκτός αν θέλουμε όλες οι υποβολές του Git να αποδοθούν σε ένα μόνο φανταστικό συγγραφέα.

Ροή εργασίας

Το Perforce Git Fusion είναι μια αμφίδρομη γέφυρα μεταξύ των ελέγχων εκδόσεων Perforce και Git. Ας ρίξουμε μια ματιά στο τι αίσθηση δημιουργείται όταν εργαζόμαστε από την πλευρά του Git. Υποθέτουμε ότι έχουμε απεικονίσει το έργο “Jam” χρησιμοποιώντας ένα αρχείο διαμόρφωσης όπως φαίνεται παραπάνω, τον οποίο μπορούμε να κλωνοποιήσουμε ως εξής:

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://ben@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

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

Όπως μπορούμε να δούμε, το αποθετήριό μας μοιάζει ακριβώς όπως οποιοδήποτε άλλο αποθετήριο Git με το οποίο μπορούμε να εργαστούμε. Υπάρχουν τρεις κλάδοι και το Git δημιούργησε έναν τοπικό κλάδο master που παρακολουθεί τον origin/master. Ας κάνουμε λίγη δουλίτσα και να δημιουργήσουμε μερικές νέες υποβολές:

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Έχουμε δύο νέες υποβολές. Τώρα ας ελέγξουμε αν έχει δουλεψει και κάποιος άλλος:

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Φαίνεται ότι κάποιος δούλεψε! Δεν θα το γνωρίζατε από αυτήν την προβολή, αλλά η υποβολή 6afeb15 δημιουργήθηκε στην πραγματικότητα χρησιμοποιώντας έναν πελάτη Perforce. Απλώς μοιάζει με μια άλλη υποβολή από τη σκοπιά του Git, και ακριβώς αυτό είναι που θέλουμε. Ας δούμε πώς αντιμετωπίζει ο διακομιστής Perforce μια υποβολή συγχώνευσης:

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

Το Git νομίζει ότι λειτούργησε. Ας ρίξουμε μια ματιά στο ιστορικό του αρχείου README από τη σκοπιά του Perforce, χρησιμοποιώντας τη λειτουργία του γραφήματος αναθεώρησης του p4v:

Γράφημα αναθεώρησης Perforce που προκύπτει από ώθηση Git.
Figure 146. Γράφημα αναθεώρησης Perforce που προκύπτει από ώθηση Git.

Εάν δεν έχουμε δει ποτέ αυτήν την προβολή προηγουμένως, μπορεί να φαίνεται συγκεχυμένη, αλλά δείχνει τις ίδιες έννοιες με ένα γραφικό πρόγραμμα προβολής του ιστορικού του Git. Εξετάζουμε το ιστορικό του αρχείου README, έτσι ώστε το δέντρο καταλόγων πάνω αριστερά να δείχνει μόνο αυτό το αρχείο καθώς αναδύεται σε διάφορους κλάδους. Στην επάνω δεξιά στήλη, έχουμε ένα οπτικό γράφημα του πώς σχετίζονται οι διάφορες αναθεωρήσεις του αρχείου και η ευρύτερη εικόνα αυτού του γραφήματος βρίσκεται στο κάτω δεξί μέρος. Η υπόλοιπη προβολή δίνεται στην προβολή λεπτομερειών για την επιλεγμένη αναθεώρηση (2 σε αυτήν την περίπτωση).

Ένα πράγμα που πρέπει να παρατηρήσουμε είναι ότι το γράφημα μοιάζει ακριβώς με το ιστορικό του Git. Το Perforce δεν είχε όνομα κλάδου για να αποθηκεύσει τις υποβολές 1 και` 2`, έτσι έκανε έναν “ανώνυμο” κλάδο στον κατάλογο .git-fusion για να τις κρατήσει. Αυτό θα συμβεί επίσης για τα κλάδους Git με όνομα που δεν αντιστοιχίζονται σε έναν κλάδο Perforce με όνομα (και μπορούμε αργότερα να τους αεπικονίσουμε σε κλάδο Perforce χρησιμοποιώντας το αρχείο διαμόρφωσης).

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

Ανακεφαλαίωση Git-Fusion

Εάν έχουμε (ή μπορούμε να αποκτήσουμε) πρόσβαση στον διακομιστή Perforce μας, το Git Fusion είναι ένας πολύ καλός τρόπος για να κάνουμε το Git και το Perforce να μιλάνε το ένας στο άλλο. Εμπλέκεται η διαμόρφωση κάποιων ρυθμίσεων, αλλά η καμπύλη μάθησης δεν είναι πολύ απότομη. Αυτή είναι μία από τις λίγες ενότητες αυτού του κεφαλαίου όπου δεν θα εμφανιστούν προειδοποιήσεις σχετικά με τη χρήση της πλήρους ισχύος του Git. Αυτό δεν σημαίνει ότι το Perforce θα είναι ευχαριστημένο από όλα όσα ρίχνουμε σε αυτό —αν προσπαθήσουμε να ξαναγράψουμε ιστορικό που έχει ήδη ωθηθεί, το Git Fusion θα την απορρίψει— αλλά το Git Fusion προσπαθεί πολύ σκληρά να νιώσει μέρος του Git. Μπορούμε ακόμα να χρησιμοποιήσουμε τις λειτουργικές υπομονάδες Git (αν και θα φαίνονται περίεργες στους χρήστες του Perforce) και να συγχωνεύσουμε κλάδους (αυτό θα καταγραφεί ως ενσωμάτωση στην πλευρά Perforce).

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

git-p4

Η git-p4 είναι μια αμφίδρομη γέφυρα μεταξύ Git και Perforce. Τρέχει εξ ολοκλήρου μέσα στο αποθετήριο Git, επομένως δεν θα χρειαστεί κανενός είδους πρόσβαση στον διακομιστή Perforce (εκτός από τα διαπιστευτήρια χρήστη, φυσικά). Η git-p4 δεν είναι τόσο ευέλικτο ούτε ολοκληρωμένο ως λύση όπως το Git Fusion, αλλά μας επιτρέπει να κάνουμε τα περισσότερα από όσα θέλουμε να κάνουμε χωρίς να επεμβαίνουμε στο περιβάλλον του διακομιστή.

Note

Θα χρειαστούμε το εργαλείο p4 κάπου στο PATH για να εργαστούμε με το git-p4. Όταν γράφεται αυτό το κείμενο, διατίθεται ελεύθερα στη διεύθυνση http://www.perforce.com/downloads/Perforce/20-User.

Εγκατάσταση

Για παράδειγμα, θα τρέχουμε τον διακομιστή Perforce από το Git Fusion OVA, όπως φαίνεται παραπάνω, αλλά θα παρακάμψουμε τον διακομιστή Git Fusion και θα πάμε κατευθείαν στον έλεγχο έκδοσης Perforce.

Για να χρησιμοποιήσουμε τον πελάτη γραμμής εντολών p4 (από τον οποίο εξαρτάται το git-p4), θα χρειαστεί να ορίσουμε μερικές μεταβλητές περιβάλλοντος:

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
Ξεκινώντας

Όπως και με ο,τιδήποτε στο Git, η πρώτη εντολή είναι να κλωνοποιήσουμε:

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

Αυτό δημιουργεί ό,τι με όρους Git καλείτια “ρηχός” κλώνος· μόνο η τελευταία αναθεώρηση Perforce εισάγεται στο Git. Ας θυμηθούμε το Perforce δεν έχει σχεδιαστεί για να παρέχει κάθε αναθεώρηση σε κάθε χρήστη. Αυτό αρκεί για να χρησιμοποιήσουμε το Git ως πελάτη Perforce, αλλά για άλλους σκοπούς δεν είναι αρκετό.

Μόλις τελειώσει, έχουμε έναν πλήρως λειτουργικό αποθετήριο Git:

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

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

$ git remote -v

Δεν υπάρχουν καθόλου απομακρυσμένες μονάδες σε αυτό το αποθετήριο. Η git-p4 δημιούργησε κάποιες ref ώστε να αναπαριστά την κατάσταση του διακομιστή και αυτές μοιάζουν με απομακρυσμένες αναφορές στην git log, αλλά δεν τις διαχειρίζεται το ίδιο το Git και δεν μπορούμε να τις ωθήσουμε.

Ροή εργασίας

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

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

Έχουμε κάνει δύο νέες υποβολές ότι είμαστε έτοιμοι να υποβάλουμε στον διακομιστή Perforce. Ας ελέγξουμε αν κάποιος άλλος δούλευε σήμερα:

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Φαίνεται ότι κάποιος δούλευε και οι master και p4/master έχουν αποκλίνει. Το σύστημα διακλάδωσης της Perforce δεν είναι όπως του Git, οπότε οι υποβολές συγχώνευσης στερούνται νοήματος. Η git-p4 συνιστά να αλλάξουμε τη βάση των υποβολών μας και μάλιστα μας παρέχει και μια συντόμευση για να το κάνουμε:

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Είναι ίσως φανερό από την έξοδο, αλλά η git p4 rebase είναι μια συντόμευση για το git p4 sync ακολουθούμενη από git rebase p4/master. Στην πραγματικότητα είναι λίγο πιο έξυπνο από αυτό, ειδικά όταν εργαζόμαστε με πολλαπλούς κλάδους, αλλά αυτή είναι μια καλή προσέγγιση.

Τώρα το ιστορικό μας είναι και πάλι γραμμικό και είμαστε έτοιμοι να συνεισφέρουμε τις αλλαγές μας στο Perforce. Η εντολή git p4 submit θα προσπαθήσει να δημιουργήσει μια νέα έκδοση Perforce για κάθε υποβολή Git μεταξύ του p4/master και master. Αν την τρέξουμε θα ανοίξει τον αγαπημένο μας επεξεργαστή και το περιεχόμενο του αρχείου θα είναι κάτι σαν αυτό:

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

Αυτό είναι ως επί το πλείστον το ίδιο περιεχόμενο που θα βλέπαμε αν τρέχαμε την p4 submit, εκτός από τα πράγματα στο τέλος που η git-p4 έχει συμπεριλάβει. Η git-p4 προσπαθεί να τιμά τις ρυθμίσεις τόσο του Git όσο και του Perforce όταν πρέπει να παράσχει ένα όνομα για μια υποβολή ή ένα σύνολο αλλαγών, αλλά σε ορισμένες περιπτώσεις θέλουμε να τις παρκάμψουμε. Για παράδειγμα, εάν η υποβολή Git που εισάγουμε γράφτηκε από έναν συνεισφέροντα που δεν διαθέτει λογαριασμό χρήστη Perforce, ίσως θέλουμε να φαίνεται ότι αυτός έγραψε τα προκύπτοντα σύνολα αλλαγών (και όχι εμείς).

Η git-p4 εισήγαγε με προσοχή το μήνυμα από την υποβολή Git ως το περιεχόμενο αυτού του συνόλου αλλαγών του Perforce, οπότε το μόνο που έχουμε να κάνουμε είναι να αποθηκεύσουμε και να το βγούμε δύο φορές (μία φορά για κάθε υποβολή). Η έξοδος που εκτυπώνεται στο κέλυφος θα φαίνεται κάπως έτσι:

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Το αποτέλεσμα είναι σαν να κάναμε μία git push, που είναι και η κοντινότερη αναλογία με αυτό που πραγματικά συνέβη.

Υπόψη ότι κατά τη διάρκεια αυτής της διαδικασίας, κάθε υποβολή Git μετατρέπεται σε ένα σύνολο αλλαγών Perforce· αν θέλουμε να τις στριμώξουμε σε ένα ενιαίο σύνολο αλλαγών, μπορούμε να το κάνουμε αυτό με μια διαδραστική αλλαγή βάσης πριν τρέξουμε την git p4 submit. Επίσης, ας σημειωθεί ότι οι αριθμοί SHA-1 όλων των υποβολών που υποβλήθηκαν ως σύνολα αλλαγών έχουν αλλάξει· αυτό συμβαίνει επειδή η git-p4 προσθέτει μια γραμμή στο τέλος κάθε υποβολής που μετατρέπει:

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

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

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Τα ιστορικά Git και Perforce αποκλίνουν μετά την 775a46f. Η πλευρά του Git έχει δύο υποβολές, στη συνέχεια μια συγχώνευση υποβολής με την κεφαλή του Perforce, έπειτα μία άλλη υποβολή. Θα προσπαθήσουμε να τα υποβάλουμε στην κορυφή ενός ενιαίου συνόλου αλλαγών στην πλευρά του Perforce. Ας δούμε τι θα συμβεί αν προσπαθήσουμε να υποβάλουμε τώρα:

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

Η σημαία -n είναι συνοτομογραφία για το --dry-run, το οποίο προσπαθεί να αναφέρει τι θα συνέβαινε εάν η εντολή submit εκτελούνταν πραγματικά. Σε αυτήν την περίπτωση, φαίνεται ότι θα δημιουργούσαμε τρία σύνολα αλλαγών Perforce, τα οποία θα αντιστοιχίζονταν στις τρεις υποβολές (όχι συγχώνευσης) που δεν υπάρχουν ακόμα στον διακομιστή Perforce. Αυτό ακούγεται ότι είναι ακριβώς αυτό που θέλουμε, ας δούμε τι έκβαση θα έχει:

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

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

Διακλάδωση

Εάν το έργο Perforce έχει πολλούς κλάδους, δεν είμαστε και τόσο άτυχοι. Η git-p4 μπορεί να το χειριστεί με τρόπο που το δίνει την αίσθηση του Git. Ας υποθέσουμε ότι η αποθήκη Perforce είναι ως εξής:

//depot
  └── project
      ├── main
      └── dev

Και ας πούμε ότι έχουμε έναν κλάδο dev, ο οποίος έχει μία προβολή spec που μοιάζει με αυτό:

//depot/project/main/... //depot/project/dev/...

Η git-p4 μπορεί να εντοπίσει αυτόματα την κατάσταση και να κάνει το σωστό:

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Ας σημειωθεί ο προδιαγραφέας “@all” στη διαδρομή αποθήκευσης· αυτό λέει στην git-p4 να κλωνοποιήσει όχι μόνο το τελευταίο σύνολο αλλαγών για αυτό το υποδέντρο, αλλά όλες τις αλλαγές που έχουν αγγίξει ποτέ αυτές τις διαδρομές. Αυτό είναι πιο κοντά στην αντίληψη του Git για έναν κλώνο, αλλά αν εργαζόμαστε σε ένα έργο με εκτενές ιστορικό, αυτό μπορεί να έχει μεγάλη διάρκεια.

Η σημαία --detect-branches λέει στην git-p4 να χρησιμοποιήσει τις προδιαγραφές κλάδων του Perforce για να απεικονίσει τους κλάδους σε ref του Git. Εάν αυτές οι απεικονίσεις δεν υπάρχουν στον διακομιστή Perforce (και αυτό είναι ένας απόλυτα έγκυρος τρόπος χρήσης του Perforce), μπορούμε να πούμε στην git-p4 ποιες είναι οι απεικονίσεις κλάδων και έχουμε το ίδιο αποτέλεσμα:

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

Η ρύθμιση της μεταβλητής διαμόρφωσης git-p4.branchList σε main:dev λέει στην git-p4 ότι οι main και dev είναι και οι δύο κλάδοι και ο δεύτερος είναι παιδί του πρώτου.

Αν τώρα κάνουμε git checkout -b dev p4/project/dev και μετά κάποιες υποβολές, η git-p4 είναι αρκετά έξυπνη ώστε να θεωρήσει ως στόχο τον σωστό κλάδο όταν κάνουμε git p4 submit. Δυστυχώς, η git-p4 δεν μπορεί να αναμίξει ρηχούς κλώνους και πολλαπλούς κλάδους· εάν έχουμε ένα τεράστιο έργο και θέλουμε να εργαστούμε σε περισσότερους από έναν κλάδους, θα πρέπει να κάνουμε git p4 clone μία φορά για κάθε κλάδο στο οποίο θέλουμε να υποβάλουμε.

Για τη δημιουργία ή την ενοποίηση κλάδων, θα πρέπει να χρησιμοποιήσουμε έναν πελάτη Perforce. Η git-p4 μπορεί μόνο να συγχρονίζει και να υποβάλλει σε υπάρχοντες κλάδους και μάλιστα μπορεί να το κάνει με μόνο μία γραμμική αλλαγή κάθε φορά. Εάν συγχωνεύσουμε δύο κλάδους στο Git και προσπαθήσουμε να υποβάλουμε τη νέα σειρά αλλαγών, όλα αυτά που θα καταγραφούν είναι κάμποσες αλλαγές αρχείων· τα μεταδεδομένα σχετικά με τους κλάδους που εμπλέκονται στην ενσωμάτωση θα χαθούν.

Ανακεφαλαίωση Git και Perforce

Η git-p4 καθιστά δυνατή τη χρήση μιας ροής εργασίας Git με έναν διακομιστή Perforce και είναι αρκετά καλή σε αυτό. Ωστόσο, είναι σημαντικό να θυμόμαστε ότι το Perforce είναι υπεύθυνο για την πηγή και χρησιμοποιούμε το Git μόνο για να εργαστούμε τοπικά. Απλά ας είμαστε πολύ προσεκτικοί σχετικά με την κοινή χρήση των υποβολών του Git· εάν διαθέτουμε έναν απομακρυσμένο κλάδο στον οποίο μπορούν να δουλεύουν άλλοι χρήστες, πρέπει να μην ωθούμε υποβολές που δεν έχουν ήδη υποβληθεί στον διακομιστή Perforce.

Αν θέλουμε να αναμίξουμε ελεύθερα τη χρήση του Perforce και του Git ως πελάτες για τον έλεγχο πηγαίου κώδικα και μπορούμε να πείσουμε τον διαχειριστή του διακομιστή να το εγκαταστήσει, το Git Fusion κάνει χρήση του Git ως πελάτης ελέγχου εκδόσεων πρώτης κατηγορίας για έναν διακομιστή Perforce.

Git and TFS

Το Git γίνεται δημοφιλές στους προγραμματιστές Windows και εάν γράφουμε κώδικα σε Windows, υπάρχει μεγάλη πιθανότητα να χρησιμοποιούμε το Team Foundation Server (TFS) της Microsoft. Το TFS είναι μια σουίτα συνεργασίας που περιλαμβάνει την παρακολούθηση ελαττωμάτων και αντικειμένων εργασίας, υποστήριξη διαδικασιών Scrum και άλλων, αναθεώρηση κώδικα και έλεγχο εκδόσεων. Υπάρχει μια μικρή σύγχυση: TFS είναι ο διακομιστής, ο οποίος υποστηρίζει τον έλεγχο του πηγαίου κώδικα χρησιμοποιώντας τόσο το Git όσο και το δικό του προσαρμοσμένο VCS, το οποίο έχουν ονομάσει TFVC (Team Foundation Version Control). Η υποστήριξη για Git είναι μια κάπως νέα δυνατότητα για το TFS (από την έκδοση 2013), έτσι όλα τα εργαλεία που προηγούνται αυτής της έκδοσης αναφέρονται στο κομμάτι του ελέγχου εκδόσεων ως “TFS”, αν και εργάζονται κυρίως με το TFVC.

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

Ποιο εργαλείο;

Στην πραγματικότητα, υπάρχουν δύο: τα git-tf και git-tfs.

Το git-tfs (που βρίσκεται στη https://github.com/git-tfs/git-tfs) είναι ένα έργο .NET και (τουλάχιστον τώρα που γράφεται αυτό το κείμενο) τρέχει μόνο σε Windows. Για να συνεργαστεί με αποθετήρια Git, χρησιμοποιεί συνδέσεις της .NET για libgit2, μια εφαρμογή του Git που προσανατολίζεται στη βιβλιοθήκη, η οποία είναι εξαιρετικά αποδοτική και επιτρέπει μεγάλη ευελιξία με τις εσωτερικές λειτουργίες ενός αποθετηρίου Git. Το Libgit2 δεν είναι μια ολοκληρωμένη εφαρμογή του Git, οπότε για να καλύψει τη διαφορά, το git-tfs καλεί τη γραμμής εντολών του πελάτη Git για κάποιες λειτουργίες κι έτσι δεν υπάρχουν τεχνητά όρια στο τι μπορεί να κάνει με τα αποθετήρια Git. Η υποστήριξη των χαρακτηριστικών του TFVC είναι πολύ ώριμη, αφού χρησιμοποιεί τα συγκροτήσεις Visual Studio για εργασίες με διακομιστές. Αυτό σημαίνει ότι θα χρειαστούμε πρόσβαση σε αυτές τις συγκροτήσεις, πράγμα που σημαίνει ότι πρέπει να εγκαταστήσουμε μια πρόσφατη έκδοση του Visual Studio (οποιαδήποτε έκδοση από την έκδοση 2010 και μετά, συμπεριλαμβανομένης της Express από την έκδοση 2012) ή το Visual Studio SDK.

Το git-tf (του οποίου το σπίτι βρίσκεται στη https://gittf.codeplex.com) είναι ένα έργο Java και ως εκ τούτου τρέχει σε οποιονδήποτε υπολογιστή με περιβάλλον εκτέλεσης Java (Java runtime environment). Συνδέεται με τα αποθετήρια Git μέσω του JGit (εφαρμογή JVM του Git), πράγμα που σημαίνει ότι δεν έχει ουσιαστικά κανένα περιορισμό όσον αφορά τις λειτουργίες του Git. Ωστόσο, η υποστήριξή του για το TFVC είναι περιορισμένη σε σύγκριση με το git-tfs —για παράδειγμα, δεν υποστηρίζει κλάδους.

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

Note

Θα χρειαστούμε πρόσβαση σε ένα αποθετήριο TFVC για να παρακολουθήσουμε αυτά τα παραδείγματα. Αυτά δεν είναι τόσο άφθονα εκεί έξω όσο τα αποθετήρια Git ή Subversion, οπότε μπορεί να χρειαστεί να δημιουργήσουμε ένα δικό μας. Το Codeplex (https://www.codeplex.com) ή το Visual Studio Online (http://www.visualstudio.com) είναι και οι δύο καλές επιλογές για κάτι τέτοιο.

Ξεκινώντας με το git-tf

Το πρώτο πράγμα που κάνουμε, όπως συμβαίνει με οποιοδήποτε πρόγραμμα Git, είναι ο κλώνος. Με το git-tf αυτό μοιάζει ως εξής:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git

Το πρώτο όρισμα είναι η διεύθυνση URL μιας συλλογής TFVC, η δεύτερη είναι της μορφής $/έργο/κλάδος και η τρίτη είναι η διαδρομή προς τον τοπικό αποθετήριο Git που πρόκειται να δημιουργηθεί (το τελευταίο είναι προαιρετικό). Το git-tf μπορεί να λειτουργήσει μόνο με έναν κλάδο κάθε φορά· εάν θέλουμε να κάνουμε checkin σε διαφορετικό κλάδο TFVC, θα πρέπει να κάνουμε έναν νέο κλώνο από αυτόν τον κλάδο.

Αυτό δημιουργεί ένα πλήρως λειτουργικό αποθετήριο Git:

$ cd project_git
$ git log --all --oneline --decorate
512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message

Αυτό ονομάζεται ρηχός κλώνος, πράγμα που σημαίνει ότι έχει ληφθεί μόνο το τελευταίο σύνολο αλλαγών. Το TFVC δεν έχει σχεδιαστεί έτσι ώστε κάθε πελάτης να έχει ένα πλήρες αντίγραφο του ιστορικού, οπότε το git-tf έχει ως προεπιλογή να παίρνει μόνο την πιο πρόσφατη έκδοση, που είναι και το πιο γρήγορο.

Αν έχουμε κάποιο χρόνο, αξίζει τον κόπο να κλωνοποιήσουμε ολόκληρο το ιστορικό του έργου, χρησιμοποιώντας την επιλογή --deep:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main \
  project_git --deep
Username: domain\user
Password:
Connecting to TFS...
Cloning $/myproject into /tmp/project_git: 100%, done.
Cloned 4 changesets. Cloned last changeset 35190 as d44b17a
$ cd project_git
$ git log --all --oneline --decorate
d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye
126aa7b (tag: TFS_C35189)
8f77431 (tag: TFS_C35178) FIRST
0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
        Team Project Creation Wizard

Παρατηρούμε ετικέτες με ονόματα όπως TFS_C35189· αυτό είναι ένα χαρακτηριστικό που μας βοηθάει να ξέρουμε ποιες υποβολές του Git σχετίζονται με το σύνολο αλλαγών του TFVC. Αυτός είναι ένας καλός τρόπος αναπαράστασης, αφού μπορούμε να δούμε με μια απλή εντολή log ποια από τις υποβολές μας σχετίζεται με ένα στιγμιότυπο που υπάρχει επίσης στο TFVC. Δεν είναι απαραίτητες (στην πραγματικότητα μπορούμε να τις απενεργοποιήσουμε με την git config git-tf.tag false) —η git-tf διατηρεί τις πραγματικές αντιστοιχίσεις υποβολών-συνόλων αλλαγών στο αρχείο .git/git-tf.

Ξεκινώντας με το git-tfs

Η κλωνοποίηση του git-tfs συμπεριφέρεται λίγο διαφορετικά:

PS> git tfs clone --with-branches \
    https://username.visualstudio.com/DefaultCollection \
    $/project/Trunk project_git
Initialized empty Git repository in C:/Users/ben/project_git/.git/
C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9
C16 = c403405f4989d73a2c3c119e79021cb2104ce44a
Tfs branches found:
- $/tfvc-test/featureA
The name of the local branch will be : featureA
C17 = d202b53f67bde32171d5078968c644e562f1c439
C18 = 44cd729d8df868a8be20438fdeeefb961958b674

Παρατηρούμε τη σημαία --with-branches. Το git-tfs είναι ικανό να απεικονίσει κλάδους TFVC σε κλάδους Git και αυτή η σημαία του λέει να δημιουργήσει έναν τοπικό κλάδο Git για κάθε κλάδο TFVC. Αυτό συνιστάται ιδιαίτερα αν έχουμε διακλαδιστεί ή συγχωνευτεί στο TFS, αλλά δεν θα λειτουργήσει με διακομιστές παλαιότερους από τον TFS 2010 —πριν από αυτήν την έκδοση, οι “κλάδοι” ήταν απλά κατάλογοι, οπότε το git-tfs δεν μπορεί να τους ξεχωρίσει από τους κανονικούς καταλόγους.

Ας ρίξουμε μια ματιά στο αποθετήριο Git που προκύπτει:

PS> git log --oneline --graph --decorate --all
* 44cd729 (tfs/featureA, featureA) Goodbye
* d202b53 Branched from $/tfvc-test/Trunk
* c403405 (HEAD, tfs/default, master) Hello
* b75da1a New project
PS> git log -1
commit c403405f4989d73a2c3c119e79021cb2104ce44a
Author: Ben Straub <ben@straub.cc>
Date:   Fri Aug 1 03:41:59 2014 +0000

    Hello

    git-tfs-id: [https://username.visualstudio.com/DefaultCollection]$/myproject/Trunk;C16

Υπάρχουν δύο τοπικοί κλάδοι, master και featureA, που αναπαριστούν το αρχικό σημείο εκκίνησης του κλώνου (Trunk στο TFVC) και έναν κλάδο-απόγονο (featureA στο TFVC). Μπορούμε επίσης να δούμε ότι το “απομακρυσμένο αποθετήριο” του tfs έχει επίσης μερικά refs: default και featureA, που αναπαριστούν κλάδους του TFVC. Το git-tfs απεικονίζει τον κλάδο που έχουμε κλωνοποιήσει από το tfs/default, και οι άλλοι παίρνουν τα δικά τους ονόματα.

Ένα άλλο πράγμα που πρέπει να παρατηρήσουμε είναι οι γραμμές git-tfs-id: στα μηνύματα υποβολών. Αντί των ετικετών, το git-tfs χρησιμοποιεί αυτά τα σημάδια για να συνδέσει τα σύνολα αλλαγών του TFVC με τις υποβολές Git. Αυτό έχει ως συνέπεια ότι οι υποβολές μας θα έχουν διαφορετικό αριθμο SHA-1 πριν και μετά την ώθησή τους στο TFVC.

Ροή εργασίας των git-tf και git-tfs

Note

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

$ git config set --local core.ignorecase=true
$ git config set --local core.autocrlf=false

Το προφανές επόμενο πράγμα που θα θελήσουμε να κάνουμε είναι να εργαστούμε στο έργο. Το TFVC και το TFS έχουν διάφορα χαρακτηριστικά που μπορεί να προσθέτουν πολυπλοκότητα στη ροή εργασίας μας:

  1. Οι κλάδοι χαρακτηριστικών που δεν αναπαριστώνται στο TFVC προσθέτουν μια μικρή πολυπλοκότητα.   Αυτό έχει να κάνει με τον πολύ διαφορετικό τρόπο με τον οποίο οι TFVC και Git αναπαριστούν κλάδους.

  2. Ύπόψη ότι το TFVC επιτρέπει στους χρήστες να ανακτήσουν τα αρχεία από τον διακομιστή, κλειδώνοντάς τα έτσι ώστε κανένας άλλος δεν μπορεί να τα επεξεργάζεται.   Αυτό προφανώς δεν θα μας εμποδίσει να τα επεξεργαστούμε στο τοπικό αποθετήριό μας, αλλά θα μπορούσε να μας δυσκολέψει όταν έρχεται η ώρα να ωθήσουμε τις αλλαγές μας στον διακομιστή TFVC.

  3. Το TFS έχει την έννοια των “φραγμένων” checkin, όπου ένας κύκλος δοκιμής TFS πρέπει να ολοκληρωθεί με επιτυχία πριν επιτραπεί η checkin.   Αυτό χρησιμοποιεί τη λειτουργία “shelve” του TFVC, την οποία δεν καλύπτουμε λεπτομερώς εδώ.   Μπορούμε να προσποιηθούμε με χειροκίνητο τρόπο με το git-tf και το git-tfs παρέχει την εντολή checkinto που έχει επίγνωση της φραγής.

Χάρη συντομίας, αυτό που θα καλύψουμε εδώ είναι το βατό μονοπάτι, το οποίο παρακάμπτει ή αποφεύγει τα περισσότερα από αυτά τα προβλήματα.

Ροή εργασίας: git-tf

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

$ git log --oneline --graph --decorate --all
* 4178a82 (HEAD, master) update code
* 9df2ae3 update readme
* d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Θέλουμε να πάρουμε το στιγμιότυπο που βρίσκεται στην 4178a82 και να το ωθήσουμε στον διακομιστή TFVC. Καταρχάς ας δούμε αν οποιοσδήποτε από τους συμπαίκτες μας έκανε κάτι από τότε που συνδεθήκαμε τελευταία φορά:

$ git tf fetch
Username: domain\user
Password:
Connecting to TFS...
Fetching $/myproject at latest changeset: 100%, done.
Downloaded changeset 35320 as commit 8ef06a8. Updated FETCH_HEAD.
$ git log --oneline --graph --decorate --all
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
| * 4178a82 (HEAD, master) update code
| * 9df2ae3 update readme
|/
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

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

  1. Μία υποβολή συγχώνευσης φαίνεται λογική ως χρήστης του Git (άλλωστε αυτό είναι που κάνει η git pull) και η git-tf μπορεί να το κάνει αυτό για μας με ένα απλό git tf pull.   Ας έχουμε υπόψη, ωστόσο, ότι το TFVC δεν σκέφτεται με αυτόν τον τρόπο και αν ωθήσουμε υποβολές συγχώνευσης, το ιστορικό μας θα αρχίσει να φαίνεται διαφορετικό στις δύο πλευρές, κάτι που μπορεί να προκαλέσει σύγχυση.   Αν σχεδιάζουμε να υποβάλουμε όλες τις αλλαγές μας ως ένα σύνολο αλλαγών, αυτή είναι ίσως η πιο εύκολη επιλογή.

  2. Η αλλαγή βάσης καθιστά το ιστορικό υποβολών μας γραμμικό, πράγμα που σημαίνει ότι έχουμε τη δυνατότητα να μετατρέψουμε καθεμία από τις υποβολές μας Git σε ένα σύνολο αλλαγών του TFVC.   Δεδομένου ότι αυτό αφήνει τις περισσότερες επιλογές ανοικτές, συνιστάται να το κάνουμε με αυτόν τον τρόπο. Το git-tf μας διευκολύνει ακόμα και με την git tf pull --rebase.

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

$ git rebase FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Τώρα είμαστε έτοιμοι να κάνουμε checkin στον διακομιστή TFVC. Το git-tf μας δίνει τις εξής επιλογές: να δημιουργήσουμε ένα ενιαίο σύνολο αλλαγών που αναπαριστά όλες τις αλλαγές από την τελευταία checkin (--shallow, που είναι και η προεπιλογή) ή να δημιουργήσουμε ένα νέο σύνολο αλλαγών για κάθε υποβολή Git (--deep). Σε αυτό το παράδειγμα, θα δημιουργήσουμε μόνο ένα σύνολο αλλαγών:

$ git tf checkin -m 'Updating readme and code'
Username: domain\user
Password:
Connecting to TFS...
Checking in to $/myproject: 100%, done.
Checked commit 5a0e25e in as changeset 35348
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Υπάρχει μια νέα ετικέτα TFS_C35348, που υποδεικνύει ότι το TFVC αποθηκεύει ακριβώς το ίδιο στιγμιότυπο με την υποβολή 5a0e25e. Είναι σημαντικό να σημειώσουμε ότι δεν είναι απαραίτητο κάθε υποβολή Git να έχει ένα ακριβές αντίγραφο στο TFVC· η υποβολή 6eb3eb5, για παράδειγμα, δεν υπάρχει σε κανένα σημείο του διακομιστή.

Αυτή είναι η κύρια ροή εργασίας. Υπάρχουν μερικά άλλα θέματα που πρέπει να έχουμε κατά νου:

  • Δεν υπάρχει διακλάδωση.   Το git-tf μπορεί να δημιουργήσει μόνο αποθετήρια Git από έναν κλάδο TFVC κάθε φορά.

  • Συνεργαζόμαστε χρησιμοποιώντας είτε TFVC είτε Git αλλά όχι και τα δύο.   Διαφορετικοί κλώνοι git-tf του ίδιου αποθετηρίου TFVC μπορεί να έχουν διαφορετικούς SHA-1 υποβολής, οι οποίοι θα μας προκαλέσουν ατελείωτους πονοκεφάλους.

  • Αν η ροή εργασίας της ομάδας μας περιλαμβάνει συνεργασία με το Git και συγχρονισμό με το TFVC περιοδικά, συνδεόμαστε μόνο στο TFVC μόνο με ένα από τα αποθετήρια Git.

Ροή εργασίας: git-tfs

Ας περάσουμε από το ίδιο σενάριο χρησιμοποιώντας το git-tfs. Ακολουθούν οι νέες υποβολές που έχουμε κάνει στον κλάδο master στο αποθετήριο Git:

PS> git log --oneline --graph --all --decorate
* c3bd3ae (HEAD, master) update code
* d85e5a2 update readme
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 (tfs/default) Hello
* b75da1a New project

Τώρα ας δούμε αν κάποιος άλλος έχει κάνει δουλειά ενώ εμείς κάναμε τις δικές μας αλλαγές:

PS> git tfs fetch
C19 = aea74a0313de0a391940c999e51c5c15c381d91d
PS> git log --all --oneline --graph --decorate
* aea74a0 (tfs/default) update documentation
| * c3bd3ae (HEAD, master) update code
| * d85e5a2 update readme
|/
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Ναι, αποδεικνύεται ότι ο συνεργάτης μας έχει προσθέσει ένα νέο σύνολο αλλαγών TFVC, το οποίο εμφανίζεται με τη νέα υποβολή aea74a0 και ο απομακρυσμένος κλάδος tfs/default μετακινήθηκε.

Όπως και με το git-tf, έχουμε δύο βασικές επιλογές για τον τρόπο επίλυσης αυτού του αποκλίνοντος ιστορικού:

  1. Αλλαγή βάσης για να διατηρήσουμε το ιστορικό γραμμικό.

  2. Συγχώνευση για να διατηρήσουμε αυτό που πραγματικά συνέβη.

Σε αυτήν την περίπτωση, θα κάνουμε ένα “βαθύ” checkin, στο οποίο κάθε υποβολή Git θα γίνει ένα σύνολο αλλαγών TFVC, οπότε θέλουμε να αλλάξουμε τη βάση.

PS> git rebase tfs/default
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
PS> git log --all --oneline --graph --decorate
* 10a75ac (HEAD, master) update code
* 5cec4ab update readme
* aea74a0 (tfs/default) update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Τώρα είμαστε έτοιμοι να ολοκληρώσουμε τη συνεισφορά μας ελέγχοντας τον κώδικά μας στον διακομιστή TFVC. Θα χρησιμοποιήσουμε την εντολή rcheckin για να δημιουργήσουμε ένα σύνολο αλλαγών TFVC για κάθε υποβολή Git στη διαδρομή από τον HEAD στον πρώτο απομακρυσμένο κλάδο tfs που βρέθηκε (η εντολή checkin θα δημιουργούσε μόνο ένα σύνολο αλλαγών, όπως η συναρμογή υποβολών στο Git).

PS> git tfs rcheckin
Working with tfs remote: default
Fetching changes from TFS to minimize possibility of late conflict...
Starting checkin of 5cec4ab4 'update readme'
 add README.md
C20 = 71a5ddce274c19f8fdc322b4f165d93d89121017
Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit...
Rebase done successfully.
Starting checkin of b1bf0f99 'update code'
 edit .git\tfs\default\workspace\ConsoleApplication1/ConsoleApplication1/Program.cs
C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b
Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit...
Rebase done successfully.
No more to rcheckin.
PS> git log --all --oneline --graph --decorate
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Παρατηρούμε πως μετά από κάθε επιτυχές checkin στον διακομιστή TFVC, το git-tfs αλλάζει τη βάση του υπόλοιπου έργου σε αυτό που μόλις έκανε. Αυτό συμβαίνει επειδή προσθέτει το πεδίο git-tfs-id στο κάτω μέρος των μηνυμάτων υποβολής, το οποίο αλλάζει τους αριθμούς SHA-1. Αυτό είναι ακριβώς όπως έχει σχεδιαστεί και δεν υπάρχει κάτι να μας ανησυχεί, αλλά θα πρέπει να γνωρίζουμε ότι συμβαίνει αυτό, ειδικά αν μοιραζόμαστε υποβολές Git με άλλους.

Το TFS έχει πολλές λειτουργίες που ενσωματώνονται με το σύστημα ελέγχου εκδόσεών του, όπως στοιχεία εργασίας, ορισθέντες αναθεωρητές, φραγμένα checkin κ.ο.κ. Η εργασία με αυτά τα χαρακτηριστικά χρησιμοποιώντας μόνο το εργαλείο γραμμής εντολών μπορεί να είναι δύσχρηστη, αλλά ευτυχώς το git-tfs μας επιτρέπει να ξεκινήσουμε ένα γραφικό εργαλείο checkin (checkintool) πολύ εύκολα:

PS> git tfs checkintool
PS> git tfs ct

Μοιάζει λίγο σαν αυτό:

`git-tfs checkintool`.
Figure 147. git-tfs checkintool.

Αυτό είναι οικείο στους χρήστες του TFS, καθώς είναι ο ίδιος διάλογος που ξεκινάει μέσα από το Visual Studio.

Το git-tfs μας επιτρέπει επίσης να ελέγχουμε τους κλάδους του TFVC από το αποθετήριο Git. Για παράδειγμα, ας δημιουργήσουμε ένα:

PS> git tfs branch $/tfvc-test/featureBee
The name of the local branch will be : featureBee
C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5
PS> git log --oneline --graph --decorate --all
* 1d54865 (tfs/featureBee) Creation branch $/myproject/featureBee
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Η δημιουργία ενός κλάδου στο TFVC σημαίνει την προσθήκη ενός συνόλου αλλαγών όπου αυτό υπάρχει αυτός ο κλάδος αυτήν τη στιγμή και αυτό προβάλλεται ως υποβολή Git. Ας σημειωθεί επίσης ότι το git-tfs δημιούργησε τον απομακρυσμένο κλάδο tfs/featureBee, αλλά ο HEAD εξακολουθεί να δείχνει στον master. Εάν θέλουμε να εργαστούμε στον νεοσύστατο κλάδο, θα θελήσουμε να βασίσουμε τις νέες υποβολές μας στην υποβολή 1d54865, ίσως δημιουργώντας έναν θεματικό κλάδο από αυτήν την υποβολή.

Ανακεφαλαίωση Git και TFS

Τα git-tf και git-tfs είναι και τα δύο εξαιρετικά εργαλεία για τη διασύνδεση με έναν διακομιστή TFVC. Μας επιτρέπουν να χρησιμοποιούμε τη δύναμη του Git τοπικά, να αποφεύγουμε να ταξιδεύουμε συνεχώς στον κεντρικό διακομιστή TFVC και να κάνουν τη ζωή μας ως προγραμματιστών πολύ πιο εύκολη, χωρίς να αναγκάζουν ολόκληρη την ομάδα μας να μεταναστεύσει στο Git. Εάν εργαζόμαστε σε Windows (που είναι και το πιο πιθανό, εφόσον η ομάδα μας χρησιμοποιεί το TFS), πιθανότατα θέλουμε να χρησιμοποιούμε το git-tfs, καθώς το σύνολο χαρακτηριστικών του είναι πιο πλήρες, αλλά αν εργαζόμαστε σε άλλη πλατφόρμα, θα θέλαμε να χρησιμοποιούμε το Γιτ-tf, το οποίο είναι πιο περιορισμένο. Όπως συμβαίνει με τα περισσότερα εργαλεία αυτού του κεφαλαίου, θα πρέπει να επιλέξουμε ένα από αυτά τα συστήματα ελέγχου εκδόσεων να είναι κανονικό (canonical) και να χρησιμοποιήσουμε το άλλο με δευτερεύοντα τρόπο —είτε το Git είτε το TFVC θα πρέπει να είναι το κέντρο συνεργασίας, αλλά όχι και τα δύο.