Git
Chapters ▾ 2nd Edition

7.3 Εργαλεία του Git - stash και clean

stash και clean

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

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

Φύλαξη της εργασίας μας σε απόθεμα

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

$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

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

	modified:   lib/simplegit.rb

Τώρα θέλουμε να αλλάξουμε κλάδο, αλλά δεν θέλουμε να υποβάλουμε ακόμα αυτά στα οποία εργαζόμασταν· έτσι θα φυλάξουμε τις αλλαγές σε ένα απόθεμα. Για να φτιάξουμε ένα απόθεμα στη στοίβα μας, εκτελούμε git stash ή git stash save:

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")

Ο κατάλογος εργασίας μας είναι καθαρός:

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

Σε αυτό το σημείο, μπορούμε εύκολα να αλλάξουμε κλάδο και να εργαστούμε αλλού· οι αλλαγές μας έχουν αποθηκευτεί στη στοίβα μας. Για να δούμε τι αποθέματα έχουμε, μπορούμε να χρησιμοποιήσουμε την εντολή git stash list:

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log

Σε αυτήν την περίπτωση, είχαν δημιουργηθεί δύο αποθέματα παλιότερα, οπότε έχουμε πρόσβαση σε τρία διαφορετικά αποθέματα. Μπορούμε να ξαναεφαρμόσουμε κάτι που είχαμε κρατήσει σε απόθεμα χρησιμοποιώντας την εντολή που εμφανίζεται στην έξοδο της βοήθειας της αρχικής εντολής stash: git stash apply. Εάν θέλουμε να εφαρμόσουμε ένα από τα παλιότερα αποθέματα, μπορούμε να το καθορίσουμε με το όνομά του, ως εξής: git stash apply stash@{2}. Εάν δεν καθορίσουμε κάποιο απόθεμα, το Git αντιλαμβάνεται ότι θέλουμε το πιο πρόσφατο και προσπαθεί να το εφαρμόσει:

$ git stash apply
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#      modified:   index.html
#      modified:   lib/simplegit.rb
#

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

Οι αλλαγές των αρχείων μας ξαναεφαρμόστηκαν, αλλά το αρχείο που είχαμε τοποθετήσει στο στάδιο καταχώρισης εμφανίστηκε εκεί. Για να γίνει αυτό, πρέπει να εκτελέσουμε την εντολή git stash apply με επιλογή ‘--index’ ώστε να πούμε στην εντολή να προσπαθήσει να ξαναεφαρμόσει και τις αλλαγές στο στάδιο καταχώρισης. Εάν είχαμε τρέξει αυτό, θα είχαμε επιστρέψει στην αρχική μας κατάσταση:

$ git stash apply --index
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#      modified:   index.html
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#      modified:   lib/simplegit.rb
#

Η επιλογή apply απλά προσπαθεί να εφαρμόσει το απόθεμα —συνεχίζουμε να το έχουμε στη στοίβα μας. Για να το μεταφέρουμε εκτός στοίβας, εκτελούμε git stash drop με το όνομα του αποθέματος που θέλουμε να ξεφορτωθούμε:

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

Μπορούμε επίσης να εκτελέσουμε την git stash pop για να εφαρμόσουμε το απόθεμα και συγχρόνως να το μεταφέρουμε εκτός στοίβας.

Note
Μετακίνηση προς την git stash push

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

Η εντολή git stash save δεν πρόκειται να μας εγκαταλείψει σύντομα, συνεπώς δεν υπάρχει λόγος ανησυχίας ότι θα εξαφανιστεί ξαφνικά. Αλλά ίσως είναι καλή τακτική να αρχίσουμε σιγά-σιγά να μετακινούμαστε προς την εναλλακτική με το push εξαιτίας της νέας λειτουργικότητας.

Διαφορετικές χρήσεις της stash

Υπάρχουν μερικές χρήσεις της stash που μπορούν επίσης να είναι χρήσιμες. Η πρώτη επιλογή που είναι αρκετά δημοφιλής είναι η επιλογή --keep-index στην εντολή stash save. Αυτή λέει στο Git να μην φυλάξει στο απόθεμα τίποτα από ό,τι έχουμε προσθέσει προηγουμένως στο στάδιο καταχώρισης με την εντολή git add.

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

$ git status -s
M  index.html
 M lib/simplegit.rb

$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
M  index.html

Μία άλλη συνηθισμένη ενέργεια που μπορεί να θέλουμε να κάνουμε με την stash είναι να φυλάξουμε σε ένα απόθεμα τα μη-παρακολουθούμενα αρχεία όπως και τα παρακολουθούμενα. Εκ προεπιλογής, η git stash αποθηκεύει μόνο αρχεία που βρίσκονται ήδη στο ευρετήριο. Εάν ζητήσουμε --include-untracked ή σκέτο -u, το Git θα αποθηκεύσει επίσης όλα τα μη-παρακολουθούμενα αρχεία που έχουμε δημιουργήσει.

$ git status -s
M  index.html
 M lib/simplegit.rb
?? new-file.txt

$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
$

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

$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end

 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

Δημιουργία κλάδου από απόθεμα

Εάν δημιουργήσαμε ένα απόθεμα, το αφήσουμε για λίγο και συνεχίσουμε στον κλάδο από τον οποίο το δημιουργήσαμε, μπορεί να έχουμε πρόβλημα να το ξαναεφαρμόσουμε. Αν η stash apply προσπαθήσει να τροποποιήσει ένα αρχείο που έχουμε ξανατροποποιήσει από τότε, θα έχουμε σύγκρουση συγχώνευσης και θα πρέπει να προσπαθήσουμε να την επιλύσουμε. Ένας ευκολότερος τρόπο να εφαρμόσουμε τις φυλαγμένες αλλαγές ξανά, είναι να εκτελέσουμε την `git stash branch, η οποία δημιουργεί έναν νέο κλάδο, ενημερώνει (checkout) την υποβολή στην οποία βρισκόμασταν όταν δημιουργήσαμε το απόθεμά μας, εφαρμόζει το απόθεμα σε αυτόν τον κλάδο και στη συνέχεια διαγράφει το απόθεμα, εφόσον αυτό έχει εφαρμοστεί με επιτυχία:

$ git stash branch testchanges
Switched to a new branch "testchanges"
# On branch testchanges
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#      modified:   index.html
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#      modified:   lib/simplegit.rb
#
Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)

Πρόκειται για μια βολική συντόμευση για να ανακτήσουμε εύκολα τη φυλαγμένη εργασία και να εργαστούμε σε αυτήν σε έναν νέο κλάδο.

Συμμάζεμα του καταλόγου εργασίας

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

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

Θα πρέπει να είμαστε πολύ προσεκτικοί με αυτήν την εντολή, αφού έχει σχεδιαστεί για να αφαιρεί αρχεία από τον κατάλογο εργασίας που δεν παρακολουθούνται. Εάν αλλάξουμε γνώμη, συχνά δεν υπάρχει τρόπος να ανακτηθούν τα περιεχομένα των αρχείων αυτών. Μια ασφαλέστερη επιλογή είναι να εκτελέσουμε την git stash --all οπότε διαγράφουμε τα πάντα, αλλά επίσης τα αποθηκεύουμε σε ένα απόθεμα.

Αν θέλουμε να ξεφορτωθούμε τα αρχεία-σκουπίδια ή να καθαρίσουμε τον κατάλογο εργασίας, αυτό μπορούμε να το κάνουμε με την git clean. Για να καταργήσουμε όλα τα μη-παρακολουθούμενα αρχεία στον κατάλογο εργασίας μας, μπορούμε να εκτελέσουμε την git clean -f -d, η οποία αφαιρεί όλα τα αρχεία καθώς και όλους τους υποκαταλόγους που αδειάζουν ως αποτέλεσμα. Το -f σημαίνει “force” (“επίβαλε”) ή “κάντο πραγματικά”.

Αν θέλουμε ποτέ να δούμε τι θα έκανε, μπορούμε να εκτελέσουμε την εντολή με την επιλογή -n, που σημαίνει “κάνε μια γενική δοκιμή και πες μου τι θα άλλαζες”.

$ git clean -d -n
Would remove test.o
Would remove tmp/

Εκ προεπιλογής, η εντολή git clean θα αφαιρέσει μόνο μη-παρακολουθούμενα αρχεία που δεν αγνοούνται. Οποιοδήποτε αρχείο ταιριάζει σε κάποιο μοτίβο στο αρχείο μας .gitignore ή άλλα αρχεία αγνόησης δεν θα αφαιρεθούν. Εάν θέλουμε να καταργήσουμε και αυτά τα αρχεία, όπως να καταργήσουμε όλα τα αρχεία .o που δημιουργούνται από μία build, ώστε να μπορούμε να κάνουμε μια εντελώς καθαρή build, μπορούμε να προσθέσουμε ένα -x στην εντολή git clean.

$ git status -s
 M lib/simplegit.rb
?? build.TMP
?? tmp/

$ git clean -n -d
Would remove build.TMP
Would remove tmp/

$ git clean -n -d -x
Would remove build.TMP
Would remove test.o
Would remove tmp/

Εάν δεν ξέρουμε τι πρόκειται να κάνει η εντολή git clean, πρέπει πάντοτε να την εκτελούμε πάντα με ένα -n για να διπλοελέγξουμε πριν αλλάξουμε το -n σε -f και το κάνουμε πραγματικό. Ο άλλος τρόπος με τον οποίο μπορούμε να είμαστε προσεκτικοί σχετικά με τη διαδικασία είναι να την εκτελέσουμε με τη σημαία -i ή “interactive”.

Αυτό θα εκτελέσει την εντολή clean σε διαδραστική λειτουργία.

$ git clean -x -i
Would remove the following items:
  build.TMP  test.o
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit
    6: help
What now>

Με αυτόν τον τρόπο μπορούμε να προχωρήσουμε βήμα-βήμα σε κάθε αρχείο ξεχωριστά ή να καθορίσουμε πρότυπα για διαγραφή διαδραστικά.