‚Rozdwojenie’ katalogu przy użyciu git

0

Wszystko zaczyna się w linii 51 git checkout test. Powstaje katalog a/ i aaa/ albo aa/ i aaa/.

$ mkdir git && cd git && git init && mkdir a && touch a/a.txt
Initialized empty Git repository in /home/inc/moje/wysypisko/tymczasowe/git/.git/
$ git add .
$ git commit -m 'Initial commit'
[main (root-commit) e997459] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a/a.txt
$ echo Text > a/a.txt 
$ git checkout -b test
Switched to a new branch 'test'
$ git checkout main
M	a/a.txt
Switched to branch 'main'
$ git mv a aa
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    a/a.txt -> aa/a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aa/a.txt

$ ls
aa
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    a/a.txt -> aa/a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aa/a.txt

$ git commit -m 'Second commit'
[main 50452c5] Second commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename {a => aa}/a.txt (100%)
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aa/a.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout test
error: Your local changes to the following files would be overwritten by checkout:
	aa/a.txt
Please commit your changes or stash them before you switch branches.
Aborting
$ git mv aa aaa
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    aa/a.txt -> aaa/a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aaa/a.txt

$ git checkout test
A	aaa/a.txt
Switched to branch 'test'
$ ls
a  aaa
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   aaa/a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aaa/a.txt

$ git checkout main
A	aaa/a.txt
Switched to branch 'main'
$ ls
aa  aaa
$ cat aa/a.txt 
$ cat aaa/a.txt 
Text

Jak się zabezpieczyć przed takim zachowaniem gita?
Poza tym udało mi się również zrobić tak, że w katalogu o starej nazwie znalazły się pliki zmodyfikowane ostatnio, ale nie umiem teraz tego odtworzyć.
Jeśli ktokolwiek myśli, że to jest błahy problem, to warto zauważyć, że wystarczy nie wykonać polecenia git add . (lub podobnego) przed git commit i wykonać git mv.

0

Ale o co chodzi, nie dałeś git add, ani stash to jest jak jest, jaki problem?

1

No prześledźmy dokładnie, co próbujesz zrobić:

  1. $ git status
    On branch main
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
    	renamed:    a/a.txt -> aa/a.txt
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
    	modified:   aa/a.txt
    

    To znaczy że jedna zmiana jest "do zacommitowania", a druga nie.

  2. $ git commit -m 'Second commit'
    [main 50452c5] Second commit
     1 file changed, 0 insertions(+), 0 deletions(-)
     rename {a => aa}/a.txt (100%)
    

    Tutaj commitujesz jedną (ten rename, ale nie modyfikację)

  3. $ git status
    On branch main
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
    	modified:   aa/a.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

    Tutaj sprawdzasz jakie masz pozostałe zmiany. Widać że jest modyfikacja w pliku aa/a.txt, która nie jest zacommitowana.

    Jeśli chciałeś dodać tą modyfikację do commita, to powinieneś wcześniej zrobić git add ., i dopiero potem git commit.

  4. $ git checkout test
    error: Your local changes to the following files would be overwritten by checkout:
    	aa/a.txt
    Please commit your changes or stash them before you switch branches.
    Aborting
    

    No a to z kolei oznacza, że jeśli zrobilbyś teraz checkout na poprzedni branch, to utraciłbyś swoje aktualnie niezestage'owane zmiany - jeśli chcesz je utracić, to dodaj flagę -f.

Także pytanie zasadnicze się pojawia - co Ty tak na prawdę chcesz zrobić?

0

Zmieniłeś mój post! ;> Zastanawiam się, czy odpowiadać. Tam były moje cudzysłowy, wiem, że można użyć ‘backtick’.
Problem pojawia się w następnej komendzie dopiero po nieudanym “git checkout test”. Wystarczy zmienić nazwę katalogu i następne “checkouty” się udadzą, ale plik w zmienionym katalogu nie jest uznawany za następcę tego zmienionego, ‘niezacommitowanego’ pliku. I pojawia się z powrotem stary katalog.
Problem ukazany jest tutaj tylko testowo. Powstał w rzeczywistości i nie udało mi się odtworzyć go w całości, ponieważ zrobiłem jeszcze tak, że nowe wersje plików były w starym katalogu. Czego następstwem była strata danych i konieczność ich odtwarzania. Czyli jeśli użyć “git mv”, to wszystko może się zdarzyć.

1
overcq napisał(a):

Problem pojawia się w następnej komendzie dopiero po nieudanym “git checkout test”. Wystarczy zmienić nazwę katalogu i następne “checkouty” się udadzą, ale plik w zmienionym katalogu nie jest uznawany za następcę tego zmienionego, ‘niezacommitowanego’ pliku. I pojawia się z powrotem stary katalog.

No tak się dzieje, i to jest spodziewane zachowanie.

Jeśli zmienisz nazwę katalogu, to ten plik staje się niewersjonowany, a ponieważ nie istnieje też w kontroli wersji, to git spokojnie zrobi checkout. Jeśli plik jest wersjonowany, lub jego wersja istnieje w branchu na których chcesz przejść, wtedy straciłbyś te zmiany i dlatego git nie robi checkout'a (chyba że zrobiłbyś -f).

Nie wiem do końca o jakiej "utracie danych" możesz mówić, bo właściwie wszystko co robi git chroni Cię przed tym.

Problem ukazany jest tutaj tylko testowo. Powstał w rzeczywistości i nie udało mi się odtworzyć go w całości, ponieważ zrobiłem jeszcze tak, że nowe wersje plików były w starym katalogu. Czego następstwem była strata danych i konieczność ich odtwarzania. Czyli jeśli użyć “git mv”, to wszystko może się zdarzyć.

git mv to jest to samo co ręczne przeniesienie plików i zrobienie potem git add .

Te dwie linijki są ze sobą równoważne:

  • git mv file somewhere/file
    
  • mv file somewhere/file
    git add somewhere/file
    
0

Faktem jest, że po ostatniej komendzie, jeśli usunie się pliki i wykona “checkout”, to “status” jest następujący:

On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   aaa/a.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    aaa/a.txt

git wypisuje błędne dane, tak jakby nowy plik istniał.
Miałem to samo, tylko ze starym katalogiem, ponieważ wykonałem więcej poleceń, których teraz nie potrafię odtworzyć: nowe pliki były w starym katalogu.
Wniosek, że “git mv” można wykonywać tylko po sprawdzeniu wszystkiego, na czysto.

1
overcq napisał(a):

Faktem jest, że po ostatniej komendzie, jeśli usunie się pliki i wykona “checkout”, to “status” jest następujący:

On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   aaa/a.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    aaa/a.txt

git wypisuje błędne dane, tak jakby nowy plik istniał.
Miałem to samo, tylko ze starym katalogiem, ponieważ wykonałem więcej poleceń, których teraz nie potrafię odtworzyć: nowe pliki były w starym katalogu.
Wniosek, że “git mv” można wykonywać tylko po sprawdzeniu wszystkiego, na czysto.

No nie, to jest nadal spodziewane zachowanie. I dodatkowo, to nie ma za wiele wspólnego z git mv, a tylko i wyłącznie z git add.

Można to łatwo zauważyć pisząc taki kod:

echo "before" > file      # wprowadzam pierwszą zmianę do pliku
git add file              # "dodaję" pierwszą zmianę
echo "after"  > file      # zmieniam plik
                          # nie robię "git add" ponownie
git status
Zmiany do złożenia:
  (użyj „git rm --cached <plik>...”, aby wycofać)
	nowy plik:       file

Zmiany nie przygotowane do złożenia:
  (użyj „git add <plik>...”, żeby zmienić, co zostanie złożone)
  (użyj „git restore <plik>...”, aby odrzucić zmiany w katalogu roboczym)
	zmieniono:       file

W git status widzę że moja pierwsza zmiana (ta z before) jest staged, czyli "do zacommitowania", a druga nie. Gdybym chciał zacommitować tą drugą, powinienem zrobić git add.
Teraz robię commit

git commit -m "Commit"

I teraz w tym commicie jest "before", nie "after". W git status zobaczę, że zmiany z dodaniem już nie ma w staged-area (bo jest zacommitowana), a zmiana z "after" jest nie-do-zacommitowania (chyba że zrobię git add).

Teraz, w Twoim przykładzie Ty usuwasz plik - jest to tak na prawdę tożsama sytuacja

touch file           # wprowadzam nowy plik
git add file         # "dodaję" plik
rm file              # usuwam plik
                     # nie robię git add
git status                     
Zmiany do złożenia:
  (użyj „git rm --cached <plik>...”, aby wycofać)
	nowy plik:       file

Zmiany nie przygotowane do złożenia:
  (użyj „git add/rm <plik>...”, żeby zmienić, co zostanie złożone)
  (użyj „git restore <plik>...”, aby odrzucić zmiany w katalogu roboczym)
	usunięto:        file

Czyli widzę dokładnie to co wcześniej (tylko tam mieliśmy treść "before" i "after", a tu mamy istniejący i nieistniejący plik - dla gita to jest to samo). Czyli najpierw mamy dodanie pliku "do zcommitowania", a usunięcie pliku jest "niedozcommitowania" i byłoby gdybyś zrobił git add. Jeśli teraz zrobię git commit, to dodanie pliku zostanie dodane do historii, a usunięcie nadal jest tylko lokalne. Czyli w repozytorium ten plik jest, a na Twoim lokalnym kompie nie.

Może rozprawmy się z paroma potencjalnymi błędami:

  • git add nie znaczy "dodaj plik", to oznacza "dodaj zmianę w pliku" (to może być edycja, dodanie linijek usunięcie linijek, przeniesienie pliku, usunięcie pliku). Dla gita to wszystko są zmiany, które trzeba dodać.
  • czy plik musi istnieć w momencie commitowania? Nie. Bo możesz mieć plik, zrobić git add (czyli przenieść go do staging-area), potem go usunąć z katalogu (ale w staging area nadal jest), i ztamtąd zrobić commit.
  • git commit nie dodaje do repozytorium tego co masz aktualnie na komputerze, a tylko i wyłącznie to co masz w staged-area (to co dodałeś przez git add).
0

Dzięki za obszerne wyjaśnienie.
Jednak mimo tego wszystkiego, łatwo się pomylić, gdy używa się git mv, i wtedy powstaje lokalnie stary i nowy katalog, co wpływa na dalsze działania użytkownika prowadzące do usunięcia danych. Jeśli git blokowałby “checkout” w przypadku niepełnego “add”, to wtedy problemu by nie było.

0
overcq napisał(a):

i wtedy powstaje lokalnie stary i nowy katalog, co wpływa na dalsze działania użytkownika prowadzące do usunięcia danych.

Czyli chcesz powiedzieć, że sam sobie usunąłeś dane, i teraz obwiniasz gita o to? :D

0

Nie usunąłem sam. Wykonałem jakieś polecenie zdalnej synchronizacji i mi usunął.
Ostatecznie to nie użytkownik jest dla programu, ale program dla użytkownika.

0

Jeśli git blokowałby “checkout” w przypadku niepełnego “add”, to wtedy problemu by nie było

Mógłbyś rozwinąć?, bo sam jestem ciekaw.

2
lion137 napisał(a):

Jeśli git blokowałby “checkout” w przypadku niepełnego “add”, to wtedy problemu by nie było

Mógłbyś rozwinąć?, bo sam jestem ciekaw.

Chodzi mu o to że git powinien zwracać uwagę na niewersjonowane pliki.

PS: Jak to napisałem, to zrozumiałem absurd tego zdania.

0

@lion137: Przemyślałem problem i właściwie wiem, na czym polega nieintuicyjność zachowania gita w tym przypadku. Użytkownik (czyli ja) oczekuje, że git po zmianie jakiejś cechy katalogu (w tym przypadku zmianie jego nazwy) zastosuje ją do plików, które w nim są. Natomiast tak się nie dzieje.

0
overcq napisał(a):

Użytkownik (czyli ja) oczekuje, że git po zmianie jakiejś cechy katalogu (w tym przypadku zmianie jego nazwy) zastosuje ją do plików, które w nim są. Natomiast tak się nie dzieje.

Dzieje się tak. Dokładnie to co opisałeś, to się dzieje.

Tylko zależy co masz na myśli mówiąc "zastosuje"? Bo ogólnie w gicie, dowolna zmiana jaką dodasz jest zauważona, tylko nie jest dodana do zacommitowania.

Jak masz folder/file w repozytorium, i zmienisz nazwę katalogu folder/ na other/, to dostaniesz coś takiego:

git status
Na gałęzi master
Zmiany do złożenia:
  (użyj „git restore --staged <plik>...”, aby wycofać)
	nowy plik:       other/file

Zmiany nie przygotowane do złożenia:
  (użyj „git add/rm <plik>...”, żeby zmienić, co zostanie złożone)
  (użyj „git restore <plik>...”, aby odrzucić zmiany w katalogu roboczym)
	usunięto:        folder/file

Czyli wtedy dodanie pliku other/file jest "do zacommitowaina", ale usunięcie poprzedniego nie. Musisz je oznaczyć, robiąc git add other (czyli dodać nową nazwę folderu na "do zacommitowania"). Jeśli zrobisz git add, to dosatniesz coś takiego:

git add .
git status
Na gałęzi master
Zmiany do złożenia:
  (użyj „git restore --staged <plik>...”, aby wycofać)
	zmieniono nazwę: folder/file -> other/file

Cały Twój problem się sprowadza do tego że nie wywołałeś git add. To o czym mówisz, to jest tak jakbyś chciał, żeby automatycznie były automatycznie dodawane do staged-area (staged-area to miejsce gdzie są wszystkie pliki na których zrobiłeś git add). To jest dokładnie to co się dzieje, jak po prostu edytujesz plik i spróbujesz go zacommitować - nie możesz, bo git powie "nothing to commit", Musisz oznaczyć zmiane do zacommitowania (nie plik, tylko zmianę), robiąć git add. Wtedy możesz je normalnie zacommitować.

tl;dr;

To wygląda tak jakbyś myślał o tym że git add dodaje pliki - a to nie prawda. git add dodaje zmiany (edycje, przeniesienie, usunięcie). Przeniesienie pliku do innego katalogu (czy jak chcesz to nazwać, "zmiana nazwy katalogu") to jest zmiana, na której musisz zrobić git add. Jeśli nie, to nie zrobisz na niej commita.

1 użytkowników online, w tym zalogowanych: 0, gości: 1