Μετάβαση στο περιεχόμενο

Προσωρινή αποθήκευση

Το Git stash αποθηκεύει τις αλλαγές του καταλόγου εργασίας και του ευρετηρίου, ώστε να μεταβείτε σε άλλη εργασία και να τις επαναφέρετε αργότερα. Το Pitmaster υλοποιεί το stash χρησιμοποιώντας το refs/stash και το reflog, στη διάταξη αποθήκευσης του git. Δεν υπάρχει μέθοδος stash στο Repository· χρησιμοποιήστε την παρακάτω κλάση Stash.

Δύο γνωστά προβλήματα: όταν υπάρχουν περισσότερες από μία εγγραφές, το drop() (και επομένως το pop()) αφήνει την εγγραφή στη λίστα (#10), ενώ το apply() αντικαθιστά μη αποθηκευμένες αλλαγές σε αρχεία που δεν αγγίζει το stash (#11). Εφαρμόζετε stash μόνο σε καθαρό κατάλογο εργασίας.

Πώς λειτουργεί το stash

Κάθε εγγραφή stash είναι ένα commit με δύο γονείς:

  • Γονέας 0: το commit HEAD τη στιγμή του stash (η βασική κατάσταση)
  • Γονέας 1: ένα commit που καταγράφει την κατάσταση του ευρετηρίου (σταδιοποιημένες αλλαγές)

Το δέντρο του ίδιου του commit stash καταγράφει την κατάσταση του δέντρου εργασίας (μαζί με τις μη σταδιοποιημένες αλλαγές).

Η «στοίβα» stash είναι το reflog του refs/stash. Το πιο πρόσφατο stash είναι το ίδιο το refs/stash. Οι παλαιότερες εγγραφές είναι καταχωρίσεις reflog: stash@{0}, stash@{1} κ.λπ.

Κώδικας
refs/stash -> stash commit (worktree state)
                parent[0] -> HEAD at time of stash
                parent[1] -> index state commit
                               parent[0] -> HEAD at time of stash

Δημιουργία αντικειμένου Stash

PHP
use Pitmaster\Stash\Stash;

$stash = new Stash(
    $repo->objectDatabase(),
    $repo->refDatabase(),
    $repo->gitDir(),
    $repo->workDir(),
);

Push (αποθήκευση αλλαγών)

PHP
// public function push(string $message = '', bool $includeUntracked = false): ObjectId
$stashId = $stash->push('Work in progress on login');
echo "Saved: {$stashId->hex}\n";

// Also stash untracked files
$stashId = $stash->push('With new files', includeUntracked: true);

Το Push εκτελεί τα εξής:

  1. Διαβάζει το τρέχον commit HEAD
  2. Δημιουργεί ένα δέντρο από το τρέχον ευρετήριο (σταδιοποιημένη κατάσταση)
  3. Δημιουργεί ένα «commit ευρετηρίου» με γονέα το HEAD
  4. Δημιουργεί ένα δέντρο από τον τρέχοντα κατάλογο εργασίας (μαζί με τις μη σταδιοποιημένες αλλαγές)
  5. Δημιουργεί το commit stash (δέντρο worktree, γονείς = HEAD + commit ευρετηρίου)
  6. Ενημερώνει το refs/stash ώστε να δείχνει στο commit stash
  7. Προσθέτει μια εγγραφή reflog για τη στοίβα
  8. Επαναφέρει τον κατάλογο εργασίας και το ευρετήριο στην κατάσταση HEAD

Μετά το push, τα παρακολουθούμενα αρχεία αντιστοιχούν στο HEAD. Τα μη παρακολουθούμενα αρχεία παραμένουν, εκτός αν έχει οριστεί το includeUntracked· τότε αφαιρούνται και αποθηκεύονται στο δικό του δέντρο του commit του stash (το git τα αποθηκεύει σε τρίτο γονικό commit, οπότε το git τα βλέπει ως παρακολουθούμενες αλλαγές). Το push() δημιουργεί μια εγγραφή ακόμη κι όταν δεν έχει αλλάξει τίποτα, οπότε το git αναφέρει «No local changes to save».

Με προεπιλεγμένο μήνυμα

PHP
$stashId = $stash->push();
// Message: "WIP on <branch>: <short hash> <HEAD subject>"
// With a message: "On <branch>: <message>"

Με αποσυνδεδεμένο HEAD, το <branch> είναι HEAD.

Pop (εφαρμογή και αφαίρεση)

PHP
$stash->pop();

Εφαρμόζει την κορυφαία εγγραφή stash στον κατάλογο εργασίας και την αφαιρεί από τη στοίβα. Ισοδυναμεί με apply() και έπειτα drop().

Pop συγκεκριμένης εγγραφής

PHP
$stash->pop(1);  // Pop stash@{1}

Apply (επαναφορά χωρίς αφαίρεση)

PHP
$stash->apply();      // Apply stash@{0}
$stash->apply(2);     // Apply stash@{2}

Το Apply συγχωνεύει το stash στο αρχείο του τρέχοντος HEAD, αρχείο προς αρχείο (βάση stash, τρέχον HEAD, δέντρο stash). Η εγγραφή stash παραμένει στη στοίβα και το ευρετήριο δεν επηρεάζεται. Οι συγκρούσεις αφήνουν στα αρχεία δείκτες με ετικέτες Updated upstream και Stashed changes και προκαλούν Pitmaster\Exceptions\MergeConflictException· το pop() διατηρεί τότε την εγγραφή. Το Apply δεν ελέγχει πρώτα για τοπικές αλλαγές (δείτε τη σημείωση στην αρχή).

Λίστα εγγραφών

PHP
$entries = $stash->listEntries();

foreach ($entries as $entry) {
    echo "stash@{{$entry['index']}}: {$entry['message']}\n";
    // stash@{0}: On main: Work in progress on login
    // stash@{1}: WIP on main: f707601 Add login form
}

Κάθε εγγραφή περιλαμβάνει:

  • index: τη θέση stash (0 = πιο πρόσφατη)
  • message: το μήνυμα stash
  • hash: το hash του commit stash

Drop (αφαίρεση χωρίς εφαρμογή)

PHP
$stash->drop();       // Drop stash@{0}
$stash->drop(1);      // Drop stash@{1}

Αν αφαιρεθεί η μοναδική εγγραφή, αφαιρούνται το refs/stash και το reflog του. Όταν υπάρχουν περισσότερες εγγραφές, το drop() μετακινεί προς το παρόν μόνο το refs/stash και αφήνει το reflog αμετάβλητο, οπότε η εγγραφή παραμένει στο listEntries() (#10).

Λεπτομέρειες αποθήκευσης

Δομή commit stash

Κώδικας
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>

<stash message>

Δομή commit ευρετηρίου

Κώδικας
commit <hash>
tree <index-tree-hash>
parent <HEAD-hash>
author <current identity>
committer <current identity>

index on <branch>: <short hash> <HEAD subject>

Η ταυτότητα προέρχεται από το GIT_COMMITTER_NAME, έπειτα το GIT_AUTHOR_NAME, μετά το user.name στη ρύθμιση αποθετηρίου και τέλος το Pitmaster (με την ίδια σειρά για το email, με κατάληξη στο pitmaster@example.invalid). Η ημερομηνία προέρχεται από το GIT_COMMITTER_DATE, έπειτα το GIT_AUTHOR_DATE και τέλος την τρέχουσα ώρα. Σε αντίθεση με τα commit, το stash δεν διαβάζει τις σταθερές PITMASTER_*.

Αποθήκευση Reflog

Οι εγγραφές stash αποθηκεύονται στο .git/logs/refs/stash:

Κώδικας
0000000000000000000000000000000000000000 abc123... Name <email> 1700000000 +0000	WIP on main: f707601 Add login form
abc123... def456... Name <email> 1700000001 +0000	On main: Work in progress on login

Το reflog διαβάζεται με χρονολογική σειρά. Η λίστα stash αντιστρέφει τη σειρά του (νεότερο πρώτα). Το reflog stash γράφεται ανεξάρτητα από το core.logallrefupdates.

Χρήση CLI

Κέλυφος
# Save current changes
./vendor/bin/pitmaster stash
./vendor/bin/pitmaster stash push
./vendor/bin/pitmaster stash push "Work in progress on login"

# Apply and remove top entry
./vendor/bin/pitmaster stash pop

# Apply without removing
./vendor/bin/pitmaster stash apply

# List all entries
./vendor/bin/pitmaster stash list

# Remove top entry without applying
./vendor/bin/pitmaster stash drop

Το μήνυμα είναι το όρισμα μετά το push (ή το save)· το -m δεν υποστηρίζεται. Τα pop, apply και drop λειτουργούν πάντα με το stash@{0}· το CLI δεν έχει --include-untracked.

Περιορισμοί

  • Υποστηρίζεται μόνο stash ολόκληρου του καταλόγου εργασίας (χωρίς --keep-index)· μη παρακολουθούμενα αρχεία μόνο μέσω του push($message, includeUntracked: true)
  • Δεν υπάρχει λειτουργία --patch (διαδραστική επιλογή τμημάτων)
  • Τα Pop/apply επαναφέρουν την κατάσταση του καταλόγου εργασίας· δεν επαναφέρουν το ευρετήριο. Οι σταδιοποιημένες αλλαγές επανέρχονται ως μη σταδιοποιημένες και τα νέα σταδιοποιημένα αρχεία επανέρχονται ως μη παρακολουθούμενα
  • Το stash@{n} δεν γίνεται δεκτό από το Repository::resolve()
  • Το stash σε συνδεδεμένο worktree χρησιμοποιεί λανθασμένη θέση ρυθμίσεων και reflog (#14)