Είμαι νέος στην πολυπλοκότητα της διακλάδωσης του Git. Πάντα εργάζομαι σε ένα μόνο υποκατάστημα και δεσμεύω αλλαγές και στη συνέχεια περιοδικά σπρώχνω στην απομακρυσμένη προέλευσή μου.
Κάπου πρόσφατα, έκανα μια επαναφορά κάποιων αρχείων για να τα βγάλω από το commit staging και αργότερα έκανα ένα rebase -i
για να απαλλαγώ από μερικές πρόσφατες τοπικές commits. Τώρα βρίσκομαι σε μια κατάσταση που δεν καταλαβαίνω ακριβώς.
Στην περιοχή εργασίας μου, το git log
δείχνει ακριβώς αυτό που θα περίμενα - είμαι στο σωστό τρένο με τα commits που δεν ήθελα να φύγουν, και τα νέα εκεί, κλπ.
Αλλά μόλις έσπρωξα στο απομακρυσμένο αποθετήριο, και αυτό που υπάρχει εκεί είναι διαφορετικό-- μερικά από τα commits που είχα σκοτώσει στο rebase σπρώχτηκαν, και τα νέα που δεσμεύτηκαν τοπικά δεν είναι εκεί.
Νομίζω ότι το "master/origin" έχει αποσπαστεί από το HEAD, αλλά δεν είμαι 100% ξεκάθαρος για το τι σημαίνει αυτό, πώς να το απεικονίσω με τα εργαλεία γραμμής εντολών και πώς να το διορθώσω.
Κατ' αρχάς, ας διευκρινίσουμε τι είναι το HEAD και τι σημαίνει όταν είναι αποσυνδεδεμένο.
Το HEAD είναι το συμβολικό όνομα για την τρέχουσα ελεγχόμενη δέσμευση. Όταν το HEAD δεν είναι αποσυνδεδεμένο (η "κανονική"1 κατάσταση: έχετε έναν κλάδο που έχει ελεγχθεί), το HEAD δείχνει στην πραγματικότητα το "ref" ενός κλάδου και ο κλάδος δείχνει τη δέσμευση. Το HEAD είναι επομένως "προσαρτημένο" σε έναν κλάδο. Όταν κάνετε μια νέα δέσμευση, ο κλάδος στον οποίο δείχνει το HEAD ενημερώνεται ώστε να δείχνει τη νέα δέσμευση. Το HEAD ακολουθεί αυτόματα, αφού απλώς δείχνει στον κλάδο.
git symbolic-ref HEAD
δίνει refs/heads/master
Ο κλάδος με το όνομα "master" ελέγχεται.git rev-parse refs/heads/master
yield 17a02998078923f2d62811326d130de991d1a95a
Αυτή η δέσμευση είναι η τρέχουσα κορυφή ή "κεφαλή" του κλάδου master.git rev-parse HEAD
δίνει επίσης 17a02998078923f2d62811326d130de991d1a95a
Αυτό σημαίνει ότι είναι μια "συμβολική αναφορά". Δείχνει σε ένα αντικείμενο μέσω κάποιας άλλης αναφοράς.Έχουμε HEAD
→ refs/heads/master
→ 17a02998078923f2d62811326d130de991d1a95a
Όταν το HEAD αποσυνδέεται, δείχνει απευθείας σε μια δέσμευση - αντί να δείχνει έμμεσα σε μια δέσμευση μέσω ενός κλάδου. Μπορείτε να σκεφτείτε ότι ένα detached HEAD βρίσκεται σε έναν ανώνυμο κλάδο.
git symbolic-ref HEAD
αποτυγχάνει με fatal: ref HEAD is not a symbolic ref
git rev-parse HEAD
δίνει 17a02998078923f2d62811326d130de991d1a95a
Εφόσον δεν είναι συμβολική αναφορά, πρέπει να δείχνει απευθείας στην ίδια τη δέσμευση.Έχουμε HEAD
→ 17a02998078923f2d62811326d130de991d1a95a
Το σημαντικό πράγμα που πρέπει να θυμάστε με ένα detached HEAD είναι ότι αν η δέσμευση στην οποία δείχνει δεν αναφέρεται αλλιώς (καμία άλλη αναφορά δεν μπορεί να φτάσει σε αυτήν), τότε θα γίνει "κρεμασμένη" όταν κάνετε checkout κάποια άλλη δέσμευση. Τελικά, τέτοιες "κρεμασμένες" δεσμεύσεις θα κλαδευτούν μέσω της διαδικασίας συλλογής σκουπιδιών (από προεπιλογή, διατηρούνται για τουλάχιστον 2 εβδομάδες και μπορεί να διατηρηθούν περισσότερο, καθώς αναφέρονται από το reflog του HEAD).
1</sup>, Είναι απολύτως εντάξει να κάνετε "κανονική" δουλειά με ένα αποσπασμένο HEAD, απλά πρέπει να παρακολουθείτε τι κάνετε για να μην χρειάζεται να ψαρεύετε το ιστορικό που έχει πέσει από το reflog.
Τα ενδιάμεσα βήματα ενός διαδραστικού rebase γίνονται με ένα detached HEAD (εν μέρει για να αποφευχθεί η ρύπανση του reflog του ενεργού κλάδου). Αν ολοκληρώσετε την πλήρη λειτουργία rebase, θα ενημερώσει τον αρχικό σας κλάδο με το αθροιστικό αποτέλεσμα της λειτουργίας rebase και θα επανασυνδέσει το HEAD στον αρχικό κλάδο. Η εικασία μου είναι ότι δεν ολοκληρώσατε ποτέ πλήρως τη διαδικασία rebase- αυτό θα σας αφήσει με ένα αποσυνδεδεμένο HEAD που δείχνει στη δέσμευση που επεξεργάστηκε πιο πρόσφατα η λειτουργία rebase.
Για να ανακάμψετε από την κατάστασή σας, θα πρέπει να δημιουργήσετε έναν κλάδο που δείχνει στη δέσμευση που δείχνει αυτή τη στιγμή το αποσπασμένο HEAD σας:
git branch temp
git checkout temp
(αυτές οι δύο εντολές μπορούν να συντομογραφηθούν ως git checkout -b temp
)</sub>,
Αυτό θα επανασυνδέσει το HEAD σας στο νέο κλάδο temp
.
Στη συνέχεια, θα πρέπει να συγκρίνετε την τρέχουσα δέσμευση (και το ιστορικό της) με τον κανονικό κλάδο στον οποίο αναμένεται να εργάζεστε:
git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp
(Πιθανόν να θελήσετε να πειραματιστείτε με τις επιλογές καταγραφής: προσθέστε -p
, αφήστε το --pretty=...
για να δείτε ολόκληρο το μήνυμα καταγραφής, κ.λπ.)
Αν ο νέος σας κλάδος temp
φαίνεται καλός, ίσως θελήσετε να ενημερώσετε (π.χ.) τον master
για να δείχνει σε αυτόν:
git branch -f master temp
git checkout master
(αυτές οι δύο εντολές μπορούν να συντομογραφηθούν ως git checkout -B master temp
)</sub>,
Στη συνέχεια μπορείτε να διαγράψετε τον προσωρινό κλάδο:
git branch -d temp
Τέλος, πιθανότατα θα θελήσετε να προωθήσετε το επανιδρυμένο ιστορικό:
git push origin master
Μπορεί να χρειαστεί να προσθέσετε το --force
στο τέλος αυτής της εντολής για να pushσετε αν ο απομακρυσμένος κλάδος δεν μπορεί να "προωθηθεί" γρήγορα στο νέο commit (δηλαδή, απορρίψατε, ή ξαναγράψατε κάποιο υπάρχον commit, ή με άλλο τρόπο ξαναγράψατε κάποιο κομμάτι του ιστορικού).
Αν ήσασταν στη μέση μιας λειτουργίας rebase, μάλλον θα πρέπει να την καθαρίσετε. Μπορείτε να ελέγξετε αν ήταν σε εξέλιξη μια rebase αναζητώντας τον κατάλογο .git/rebase-merge/
. Μπορείτε να καθαρίσετε χειροκίνητα την εν εξελίξει rebase απλά διαγράφοντας αυτόν τον κατάλογο (π.χ. αν δεν θυμάστε πλέον τον σκοπό και το πλαίσιο της ενεργής λειτουργίας rebase). Συνήθως θα χρησιμοποιούσατε το git rebase --abort
, αλλά αυτό κάνει κάποια επιπλέον επαναφορά που μάλλον θέλετε να αποφύγετε (μετακινεί το HEAD πίσω στον αρχικό κλάδο και το επαναφέρει πίσω στην αρχική δέσμευση, κάτι που θα αναιρέσει μέρος της δουλειάς που κάναμε παραπάνω).
Κοιτάξτε εδώ για βασική εξήγηση της αποκολλημένης κεφαλής:
http://git-scm.com/docs/git-checkout
Γραμμή εντολών για να το οπτικοποιήσετε:
git branch
ή
git branch -a
θα λάβετε αποτελέσματα όπως παρακάτω:
* (no branch)
master
branch1
Το * (χωρίς διακλάδωση)
δείχνει ότι βρίσκεστε σε αποκομμένο κεφάλι.
Θα μπορούσατε να είχατε φτάσει σε αυτή την κατάσταση κάνοντας ένα git checkout somecommit
κλπ. και θα σας είχε προειδοποιήσει με το εξής:
Βρίσκεστε στην κατάσταση 'detached HEAD'. Είστε Μπορείτε να κοιτάξετε γύρω σας, να κάνετε πειραματικές αλλαγές και να τις δεσμεύσετε, και μπορείτε να να απορρίψετε οποιαδήποτε μεταβίβαση κάνετε σε αυτή την κατάσταση. κατάσταση χωρίς να επηρεάσετε κανέναν κλάδο εκτελώντας ένα άλλο checkout. >, Εάν θέλετε να δημιουργήσετε έναν νέο κλάδο για να να διατηρήσετε τις δεσμεύσεις που δημιουργείτε, μπορείτε να το κάνετε (τώρα ή αργότερα) χρησιμοποιώντας το -b με την εντολή την εντολή checkout ξανά. Παράδειγμα: >, git checkout -b new_branch_name
Τώρα, για να τα μεταφέρουμε στο master:
Κάντε ένα git reflog
ή ακόμα και απλά git log
και σημειώστε τις δεσμεύσεις σας. Τώρα git checkout master
και git merge
τα commits.
git merge HEAD@{1}
Επεξεργασία:
Για να προσθέσετε, χρησιμοποιήστε το git rebase -i
όχι μόνο για να διαγράψετε / σκοτώσετε commits που δεν χρειάζεστε, αλλά και για να τα επεξεργαστείτε. Απλά αναφέρετε "edit" στη λίστα commit και θα μπορείτε να τροποποιήσετε το commit σας και στη συνέχεια να εκδώσετε ένα git rebase --continue
για να προχωρήσετε. Αυτό θα εξασφάλιζε ότι δεν θα ερχόσασταν ποτέ σε ένα αποσυνδεδεμένο HEAD.
Απλά εκτελέστε git checkout -b mynewbranch
.
Στη συνέχεια, εκτελέστε το git log
, και θα δείτε ότι το commit είναι τώρα HEAD
σε αυτό το νέο branch.