Jak zaktualizować git shallow clone?

Tło

(dla tl;dr, patrz # pytania poniżej)

Mam wiele płytkich klonów repozytorium git. Używam płytkich klonów, ponieważ jest dużo mniejszy w porównaniu do brudnego klonu. Każdy sklonowany robi około git clone --single-branch --depth 1 <git-repo-url> <dir-name>.

To działa dobrze, tylko nie widzę, jak to zaktualizować.

Kiedy klonuję za pomocą znacznika, aktualizacja nie ma znaczenia, ponieważ znacznik jest zamrożony w czasie (jak to Rozumiem). W tym przypadku, jeśli chcę zaktualizować, oznacza to, że chcę sklonować przez kolejny tag, więc po prostu rm -rf <dir-name> i klonować ponownie.

Sprawy stają się bardziej skomplikowane, gdy sklonuję głowę gałęzi master, a później chcę ją zaktualizować.

Próbowałem git pull --depth 1 ale chociaż nie mam nic do zdalnego repozytorium, to narzeka, że nie wie kim jestem.

Próbowałem git fetch --depth 1, ale chociaż wydaje się, że coś aktualizuje, sprawdziłem, że nie jest aktualne (niektóre pliki w zdalnym repozytorium mają inną zawartość niż te na moim klon).

Po https://stackoverflow.com/a/20508591/279335 , próbowałem git fetch --depth 1; git reset --hard origin/master, ale dwie rzeczy: po pierwsze nie rozumiem, dlaczego git reset jest potrzebny, po drugie, chociaż pliki wydają się być aktualne, niektóre stare pliki pozostają, a git clean -df nie usuwa tych plików.

Pytania

Niech klon utworzony za pomocą git clone --single-branch --depth 1 <git-repo-url> <dir-name>. Jak go zaktualizować, aby osiągnąć ten sam wynik co rm -rf <dir-name>; git clone --single-branch --depth 1 <git-repo-url> <dir-name>? A może {[1] } i znowu klon to jedyny sposób?

Uwaga

To nie jest duplikat Jak zaktualizować płytko sklonowany podmoduł bez zwiększania głównego rozmiaru repo, ponieważ odpowiedź nie spełnia moich oczekiwań i używam prostych repozytoriów ,a nie podmodułów (o których Nie wiem).

Author: Community, 2016-12-10

2 answers

[nieco przeredagowane i sformatowane] biorąc pod uwagę klon utworzony za pomocą git clone --single-branch --depth 1 url directory, Jak mogę go zaktualizować, aby osiągnąć ten sam wynik co rm -rf directory; git clone --single-branch --depth 1 url directory?

Zauważ, że --single-branch jest domyślnym przy użyciu --depth 1. Gałąź (pojedyncza) jest tą, którą dajesz -b. Jest długa strona, która idzie tutaj o użyciu -b z tagami, ale zostawię to na później. Jeśli nie używasz -b, Twój Git pyta" upstream " Git-the Git at url-which branch to wymeldował się i udaje, że użyłeś -b thatbranch. Oznacza to, że ważne jest, aby zachować ostrożność podczas używania --single-branch bez -b aby upewnić się, że bieżąca gałąź tego repozytorium jest rozsądna, i oczywiście, kiedy wykonasz użyj -b, aby upewnić się, że podany argument branch naprawdę nazywa gałąź, a nie znacznik.

Prosta odpowiedź jest w zasadzie ta, z dwoma niewielkimi zmianami:

Po https://stackoverflow.com/a/20508591/279335, próbowałem git fetch --depth 1; git reset --hard origin/master, ale dwie rzeczy: po pierwsze nie rozumiem, dlaczego git reset jest potrzebny, po drugie, chociaż pliki wydają się być aktualne, niektóre stare pliki pozostają i git clean -df nie usuwa tych plików.

Dwie niewielkie zmiany to: upewnij się, że używasz origin/branchname zamiast tego i dodaj -x (git clean -d -f -x lub git clean -dfx) do kroku git clean. Jeśli chodzi o dlaczego , to robi się trochę bardziej skomplikowane.

Co się dzieje na

bez --depth 1, krok {[33] } wywołuje drugi Git i pobiera z niego listę nazw gałęzi i odpowiadających im identyfikatorów skrótu zatwierdzania. Oznacza to, że znajduje listę wszystkich gałęzi upstream i ich bieżących commitów. Następnie, ponieważ posiadasz --single-branch repozytorium, Twój Git wyrzuca wszystkie gałęzie poza pojedynczą gałęzią i przenosi wszystko, czego Git potrzebuje, aby połączyć bieżący commit z powrotem do commitów, które już masz w swoim repozytorium. repozytorium.

z --depth 1, Twój Git nie przeszkadza w łączeniu nowego commita ze starszymi historycznymi commitami. Zamiast tego, uzyskuje tylko jeden commit i inne obiekty Git potrzebne do wykonania tego jednego commita. Następnie pisze dodatkowy wpis "shallow graft", aby oznaczyć jeden commit jako nowy commit pseudo-root.

Zwykłe (nie płytkie) klonowanie i pobieranie

To wszystko jest związane z tym, jak Git zachowuje się, gdy używasz normalnego (non-shallow, nie-single-branch) clone: git fetch wywołuje Git, pobiera listę wszystkiego, a następnie przynosi to, czego jeszcze nie masz . To dlatego początkowy klon jest tak powolny, a pobieranie do aktualizacji jest zwykle tak szybkie: gdy otrzymasz pełny klon, aktualizacje rzadko mają bardzo wiele do przekazania: może kilka commitów, może kilkaset, a większość z tych commitów też nie potrzebuje zbyt wiele więcej.

Historia repozytorium jest tworzona z commitów. Każdy commit nazywa swój rodzic commit (lub dla merges, parent commits, liczba mnoga), w łańcuchu, który przechodzi wstecz od "ostatniego commita", do poprzedniego commita, do jakiegoś bardziej-przodkowego commita, i tak dalej. Łańcuch ostatecznie zatrzymuje się, gdy osiągnie commit, który nie ma rodzica, np. pierwszy commit w repozytorium. Ten rodzaj commita to root commit.

Czyli możemy narysować wykres commitów. W naprawdę prostym repozytorium wykres jest tylko prostą linia, ze wszystkimi strzałkami skierowanymi do tyłu:

o <- o <- o <- o   <-- master

Nazwa master wskazuje na czwarty i ostatni commit, który wskazuje na trzeci, który wskazuje na drugi, który wskazuje na pierwszy.

Każdy commit niesie ze sobą pełną migawkę wszystkich plików, które wchodzą w ten commit. Pliki, które w ogóle nie zostały zmienione, są dzielone pomiędzy tymi commitami: czwarty commit "pożycza" niezmienioną wersję od trzeciego commita, który "pożycza" to od drugiego, i tak dalej. Dlatego każdy commit nazywa wszystkie "obiekty Git", których potrzebuje, a Git albo Znajduje te obiekty lokalnie-ponieważ już je posiada - albo Używa protokołu fetch, aby przenieść je z innego, wyższego Gita. Istnieje format kompresji o nazwie "packing" , oraz specjalny wariant transferu sieciowego o nazwie "thin packs", który pozwala Gitowi zrobić to jeszcze lepiej / bardziej fantazyjnie, ale zasada jest prosta: Git potrzebuje wszystkich i tylko tych obiektów, które idą z zbiera nowe commity. Twój Git decyduje, czy posiada te obiekty, a jeśli nie, pobiera je z ich Gita.

[202]}bardziej skomplikowany, bardziej kompletny Wykres ma zazwyczaj kilka punktów, w których się rozgałęzia, niektóre gdzie się łączy i wiele nazw gałęzi wskazujących na różne końcówki gałęzi: [203]}
        o--o   <-- feature/tall
       /
o--o--o---o    <-- master
    \    /
     o--o      <-- bug/short
Branch

Tutaj branch bug/short jest scalony z powrotem do master, podczas gdy branch feature/tall jest nadal w fazie rozwoju. nazwa bug/short Można (prawdopodobnie) teraz całkowicie usunąć: nie potrzebujemy go już, jeśli skończymy robić commity na nim. Commit na końcu master nazywa dwa poprzednie commity, włączając commit na końcubug/short, więc pobierając master pobieramy commity bug/short.

Zauważ, że zarówno prosty jak i nieco bardziej skomplikowany Wykres ma tylko jeden commit główny. To dość typowe: wszystkie repozytoria posiadające commity mają co najmniej jeden commit główny, ponieważ pierwszy commit jest zawsze root commit; ale większość repozytoriów ma tylko jeden commit główny. Można jednak mieć różne commity roota, tak jak w przypadku tego wykresu:

 o--o
     \
o--o--o   <-- master

Lub ten:

 o--o     <-- orphan

o--o      <-- master

W rzeczywistości, jeden z tylko jeden master został prawdopodobnie wykonany przez połączenie orphan w master, a następnie usunięcie nazwy orphan.

Przeszczepy i zamienniki

Git od dłuższego czasu ma (prawdopodobnie chwiejne) wsparcie dla graftów, które zostało zastąpione (znacznie lepszym, w rzeczywistości-stałe) wsparcie dla generycznych zamienników. Aby je konkretnie zrozumieć, musimy dodać do powyższego pogląd, że każdy commit ma swój unikalny identyfikator. Te identyfikatory są wielkimi, brzydkimi 40-znakowymi hashami SHA-1, face0ff... i tak dalej. W rzeczywistości, każdy obiekt Git ma unikalny identyfikator, chociaż dla celów graficznych, wszystko na czym nam zależy to commity.

Do rysowania wykresów, te duże hash id są zbyt bolesne, aby ich używać, więc możemy używać jednoliterowych nazw A poprzez Z zamiast tego. Użyjmy tego wykresu jeszcze raz, ale wpisz jednoliterowe nazwy:

        E--H   <-- feature/tall
       /
A--B--D---G    <-- master
    \    /
     C--F      <-- bug/short

Commit H odnosi się z powrotem do commit E (E jest H'S rodzicem ). Commit G, który jest merge commit -co oznacza, że ma co najmniej dwoje rodziców-odnosi się do obu D i F, i tak dalej.

Zauważ, że oddział nazywa, feature/tall, master, i bug/short, każdy punkt do jednego commita. Nazwa bug/short wskazuje na commit F. To jest dlaczego commit F znajduje się na gałęzi bug/short ... tak samo jak commit C. Commit C jest włączony bug/short, ponieważ jest osiągalny od nazwy. Nazwa prowadzi nas do F, a F do C, więc C jest na gałęzi bug/short.

Zauważ jednak, że commit G, końcówka master, zmusza nas do commit F. Oznacza to, że commit F jest również na gałęzi master. jest to kluczowa koncepcja w Git: commity mogą być na jednym, wiele , lub nawet żadnych gałęzi. nazwa gałęzi jest tylko sposobem na rozpoczęcie pracy w grafie zatwierdzania. Są inne sposoby, takie jak nazwy tagów, refs/stash (które prowadzą do aktualnego schowka: każdy Schowek to w rzeczywistości kilka commitów) oraz reflogy (które zwykle są ukryte przed widokiem, ponieważ zwykle są po prostu bałaganem).

To również prowadzi nas do przeszczepów i wymian. Przeszczep jest tylko ograniczonym rodzajem zamiennika, A płytkie repozytoria używają ograniczona forma przeszczepu.1 Nie będę tutaj opisywał w pełni zamienników, ponieważ są one nieco bardziej skomplikowane, ale ogólnie rzecz biorąc, to, co Git robi dla tych wszystkich, to używanie przeszczepu lub zamiennika jako "zamiast-of". W konkretnym przypadku commity , chcemy mieć możliwość zmiany-lub przynajmniej, udawania, aby zmienić-nadrzędnego ID lub ID każdego commita ... i dla płytkich repozytoriów, chcemy być w stanie udawać, że dany commit ma nie rodzice.

1nie W bardziej ogólnym przypadku zalecałem użycie git replace zamiast, ponieważ to również było i jest , a nie. Jedynym zalecanym zastosowaniem dla przeszczepów jest-a przynajmniej było, lata temu-umieszczenie ich na tyle długo, aby uruchomić {83]} do {206]}skopiować {207]} zmienioną przeszczepioną historię, po czym należy całkowicie odrzucić przeszczepioną historię. Możesz w tym celu należy również użyć git replace, ale w przeciwieństwie do grafów, można użyć git replace na stałe lub na pół trwale, bez potrzebującego git filter-branch.


Tworzenie płytkiego klonu

Aby utworzyć płytką klon depth-1 bieżącego stanu repozytorium upstream, wybierzemy jedną z trzech nazw gałęzi-feature/tall, master, lub bug/short - i przetłumaczyć go na identyfikator commita. Wtedy napiszemy specjalny wpis, który mówi: "Kiedy zobaczysz ten commit, udawaj, że ma no commit nadrzędny, tzn. jest commitem głównym."

Powiedzmy, że wybierzemy master. Nazwa master wskazuje na commit G, więc aby utworzyć shallow klon commit G, jak zwykle otrzymujemy commit G Z Gita, ale potem piszemy specjalny wpis, który twierdzi, że commit Gma nie ma rodziców. Umieściliśmy to w naszym repozytorium, a teraz nasz wykres wygląda tak: {]}

G   <-- master, origin/master

Te identyfikatory rodziców są nadal w środku G; to tylko, że za każdym razem, gdy mamy Git use lub pokaż nam historię, natychmiast "grafts" nic-at-all on, tak że G wydaje się być głównym commitem do śledzenia historii.

Aktualizacja płytkiego klonu, który zrobiliśmy wcześniej

Ale co jeśli mamy już (głębokość-1) klon, i chcemy zaktualizować to? To nie problem. Załóżmy, że zrobiliśmy płytki klon upstream back kiedy master wskazał na commit B, zanim nowe gałęzie i poprawka błędu. Oznacza to, że obecnie mamy to:{[203]]}

B   <-- master, origin/master

Podczas gdy prawdziwym rodzicem B jest A, mamy wpis "udawaj B jest commitem root". Teraz my git fetch --depth 1, który patrzy w górę upstream ' s master - rzecz my wywołujemy origin/master - i widzimy commit G. Pobieramy commit G od góry wraz z jego obiektami, ale celowo nie pobieramy commitówD i F. Następnie aktualizujemy nasze drobno sklonowane wpisy przeszczepu, aby powiedzieć: "udawaj, że G jest również zatwierdzeniem root":

B   <-- master

G   <-- origin/master

Nasze repozytorium ma teraz dwa commity roota: nazwamaster (nadal) wskazuje na commit B, którego rodziców (nadal) udajemy, że nie istnieją, a nazwa origin/master wskazuje na G, których rodziców udajemy, że nie istnieją.

Dlatego potrzebujesz git reset

W normalnym repozytorium możesz użyć git pull, które tak naprawdę jest git fetch, a następnie git merge. Ale git merge wymaga historii, a my jej nie mamy: sfałszowaliśmy Gita z udawanymi commitami roota, a oni nie mają za sobą żadnej historii. Musimy więc zamiast tego użyć git reset.

To, co robi git reset, jest nieco skomplikowane, ponieważ może wpływać na trzy różne rzeczy: nazwę gałęzi , indeks i drzewo pracy . Widzieliśmy już, jakie są nazwy gałęzi: wskazują one po prostu na (jeden, konkretny) commit, który nazywamy końcówką gałęzi. To pozostawia indeks i drzewo pracy.

Drzewo pracy jest łatwe do wyjaśnienia: to tam znajdują się wszystkie Twoje pliki. To jest to: nie więcej i nie mniej. Jest tam po to, abyś mógł użyć Git: Git polega na przechowywaniu każdego commita, jaki kiedykolwiek powstał, na zawsze, tak aby można było je wszystkie odzyskać. Ale są w formacie bezużytecznym dla zwykłych śmiertelników. Aby być używane , plik-lub bardziej typowo, cały commit wart plików-musi zostać wyodrębniony do normalnego formatu. Drzewo robocze to miejsce, w którym to się dzieje, a następnie możesz nad nim pracować i tworzyć nowe commity za jego pomocą.

Indeks jest nieco trudniejszy do wyjaśnienia. Jest to coś specyficznego dla Gita: inne systemy kontroli wersji nie mają takiego systemu, lub jeśli mają coś takiego, to go nie ujawniają. Git ma. Indeks Git jest zasadniczo tam, gdzie przechowujesz następny commit do zrobienia, ale to oznacza, że zaczyna się od trzymania bieżącego commita, który wyodrębniłeś do drzewa roboczego, a Git używa go do szybkiego tworzenia Gita. Za chwilę powiemy o tym więcej.

To, co robi git reset --hard, to wpływać na wszystkie trzy : nazwę gałęzi, indeks i drzewo robocze. przenosi nazwę gałęzi tak, że wskazuje na (prawdopodobnie inny) commit. Następnie aktualizuje indeks, aby pasował do tego commita, i aktualizuje drzewo robocze, aby pasował do nowego indeksu.

Stąd:

git reset --hard origin/master

Mówi Gitowi, żeby szukał origin/master. Od kiedy uruchomiliśmy naszą git fetch, to teraz punkty do commit G. Git następnie tworzy nasz master-naszą bieżącą (i jedyną) gałąź-również wskazuje na commit G, a następnie aktualizuje nasz indeks i drzewo pracy. Nasz wykres wygląda teraz tak:

B   [abandoned - but see below]

G   <-- master, origin/master

Teraz master i origin/master zarówno nazwa commit G, jak i commit G są wypisywane w drzewie roboczym.

Dlaczego potrzebujesz git clean -dfx

Odpowiedź tutaj jest nieco skomplikowana, ale zazwyczaj jest to "nie" (trzeba git clean).

Kiedy do need git clean, to dlatego, że ty-lub coś, co uruchomiłeś-dodałeś pliki do swojego drzewa pracy, o czym nie powiedziałeś Gitowi. Są to pliki nie śledzone i/lub ignorowane. Użycie git clean -df usunie nie śledzone pliki (i puste katalogi); dodanie -x usunie również ignorowane pliki.

Aby dowiedzieć się więcej o różnicy między "Nie śledzonym" a "ignorowanym", zobacz tę odpowiedź .

Dlaczego nie potrzebujesz git clean: the indeks

Wspomniałem powyżej, że zazwyczaj nie musisz biegać git clean. Wynika to z indeksu. Jak powiedziałem wcześniej, indeks Gita jest głównie "kolejnym commitem do wykonania". Jeśli nigdy nie dodajesz własnych plików-jeśli używasz git checkout do sprawdzania różnych istniejących commitów, które miałeś przez cały czas, lub które dodałeś za pomocą git fetch; lub jeśli używasz git reset --hard do przenoszenia nazwy gałęzi, a także przełączania indeksu i Drzewa roboczego na inny commit-to cokolwiek jest w indeksie teraz jest ponieważ wcześniejszy git checkout (lub git reset) umieść go w indeksie, a także w drzewie roboczym.

Innymi słowy, indeks ma krótki i szybki dostęp do Git-summary lubmanifest opisujący bieżące drzewo pracy. Git używa tego, aby wiedzieć, co jest teraz w drzewie roboczym. Kiedy poprosisz Git o zmianę na inny commit, poprzez git checkout lub git reset --hard, Git może szybko porównać istniejący Indeks z nowym commitem. Dowolne pliki jeśli zostało zmienione , Git musi wyodrębnić nowy commit(i zaktualizować indeks). Wszystkie pliki, które są nowo dodane , Git musi również wyodrębnić (i zaktualizować indeks). Wszystkie pliki zniknęły - które znajdują się w istniejącym indeksie, ale nie w nowym commicie-Git muszą usunąć ... i to właśnie robi Git. Git aktualizuje, dodaje i usuwa te pliki w drzewie roboczym, zgodnie z zaleceniami porównania pomiędzy aktualnym indeksem, a nowym / align = "left" /

Oznacza to, że jeśli robisz potrzebujesz git clean, musisz zrobić coś poza Gitem, co dodało pliki. Te dodane pliki nie są w indeksie, więc z definicji, nie są śledzone i/lub ignorowane. Jeśli nie są śledzone, git clean -f usunie je, ale jeśli zostaną zignorowane, tylko git clean -fx usunie je. (Chcesz -d po prostu usunąć katalogi, które są lub stają się puste podczas czyszczenia.)

Porzucone commity i garbage collection

Wspomniałem i narysowałem w zaktualizowanym płytkim wykresie, że kiedy git fetch --depth 1 a potem git reset --hard, kończymy porzucając poprzedni commit płytkiego wykresu głębokości-1. (Na wykresie, który narysowałem, był to commit B.) Jednak w Git porzucone commity rzadko są naprawdę porzucane-przynajmniej nie od razu. Zamiast tego niektóre specjalne nazwy, takie jak ORIG_HEAD, trzymają się ich przez chwilę, a każde odniesienie - gałęzie i znaczniki są formami odniesienia-noszą z to log z "poprzednie wartości".

Możesz wyświetlić każdy reflog za pomocą git reflog refname. Na przykład git reflog master pokazuje nie tylko które zmiany master nazwały teraz, ale także które zmiany nazwały w przeszłości . Istnieje również reflog dla samego HEAD, który domyślnie pokazuje git reflog.

Wpisy Reflog ostatecznie wygasają. Ich dokładny czas trwania jest różny, ale domyślnie kwalifikują się do wygaśnięcia po 30 dniach w niektórych przypadkach i 90 dni w innych. Gdy wygasną, wpisy reflogu nie chronią już porzuconych commitów (lub, w przypadku odniesień do tagów z komentarzami, znaczniki obiektowe z komentarzami nie mają przypuszczać do przeniesienia, więc w tym przypadku nie ma przypuszczać, ale jeśli tak się stanie-jeśli zmusisz Gita do przeniesienia znacznika-będą one obsługiwane w taki sam sposób, jak wszystkie inne odniesienia).

Gdy dowolny obiekt Git-commit, tag z adnotacjami, " tree "lub" blob " (plik)-jest naprawdę niezrealizowany, Git może usuń to naprawdę.2 dopiero w tym momencie podstawowe dane repozytorium dla commitów i plików znikają. Nawet wtedy dzieje się to tylko wtedy, gdy coś działa git gc. Tak więc, płytkie repozytorium zaktualizowane git fetch --depth 1 nie jest całkiem tym samym, co świeży klon z --depth 1: płytkie repozytorium prawdopodobnie ma pewne utrzymujące się nazwy dla oryginalnych commitów i nie usunie dodatkowych obiektów repozytorium, dopóki te nazwy nie wygasną lub nie zostaną usunięte. oczyszczone.


2oprócz sprawdzania referencji, obiekty otrzymują minimalny czas zanim również wygasną. Domyślne są dwa tygodnie. Uniemożliwia to git gc usuwanie obiektów tymczasowych, które Git tworzy, ale nie ma jeszcze odniesienia do nich. Na przykład, podczas tworzenia nowego commita, Git najpierw zamienia indeks w serię tree obiektów, które odnoszą się do siebie, ale nie mają odniesienia najwyższego poziomu. Następnie tworzy nowy obiekt commit, który odnosi się do drzewa najwyższego poziomu, ale nic jeszcze nie odnosi się do commit. Na koniec aktualizuje bieżącą nazwę gałęzi. Dopóki ten ostatni krok się nie skończy, drzewa i Nowy commit są nieosiągalne!


Specjalne względy dla --single-branch i/lub płytkich klonów

Zauważyłam powyżej, że nazwa, którą nadajesz git clone -b może odnosić się do znacznika. Dla zwykłych (niepłytych lub niepłytych) klonów, działa to tak, jak można by się spodziewać: dostajesz zwykły klon, a następnie Git wykonuje git checkout przez nazwę znacznika. Rezultatem jest zwykła odłączona głowa, w zupełnie zwykłym klonie.

W przypadku klonów płytkich lub jednodrzwiowych istnieje jednak kilka niezwykłych konsekwencji. Wszystko to, w pewnym stopniu, wynika z tego, że Git pozwala na pokazanie implementacji.

Po pierwsze, jeśli używasz --single-branch, Git zmienia normalną konfigurację fetch W Nowym repozytorium. Normalna konfiguracja fetch zależy od nazwy, którą wybierzesz dla remote , ale domyślnym jest origin, więc użyję origin tutaj. Czyta:

fetch = +refs/heads/*:refs/remotes/origin/*

Ponownie, jest to normalna konfiguracja dla normalnego klonu. Ta konfiguracja mówi git fetch co pobrać , czyli "wszystkie gałęzie". Gdy używasz --single-branch, zamiast tego otrzymujesz linię fetch, która odnosi się tylko do jednej gałęzi:

fetch = +refs/heads/zorg:refs/remotes/origin/zorg
Jeśli klonujesz gałąź.

whatever branch you / align = "left" / linearkażdy przyszłość git fetch będzie posłuszny tej linii,3 więc nie weźmiesz innych gałęzi. Jeśli czy chcesz później pobrać inne gałęzie, będziesz musiał zmienić tę linię lub dodać więcej linii.

Po drugie, jeśli użyjesz --single-branch i sklonujesz znacznik , Git umieści dość dziwną linię fetch. Na przykład z git clone --single-branch -b v2.1 ... otrzymuję:

fetch = +refs/tags/v2.1:refs/tags/v2.1

To oznacza, że dostaniesz No gałęzie, i chyba że ktoś przesunął znacznik,4git fetch nic nie zrobi!

Po Trzecie, domyślne zachowanie znaczników jest nieco dziwne ze względu na sposób uzyskiwania znaczników git clone i git fetch. Pamiętaj, że znaczniki są po prostu odniesieniem do jednego commita, podobnie jak gałęzie i wszystkie inne odniesienia. Istnieją dwie kluczowe różnice między gałęziami i znacznikami, chociaż: gałęzie są oczekiwane do przeniesienia (a znaczniki nie są), a gałęzie get przemianowane (A Tagi nie).

Pamiętajcie, że przez cały powyższy czas znajdujemy, że drugi (upstream) Gita master staje się naszym origin/master, i tak dalej. Jest to przykład procesu zmiany nazwy. Zobaczyliśmy także, krótko, dokładnie jak {207]} ta zmiana nazwy działa , poprzez linię fetch =: nasz Git bierze ich refs/heads/master i zmienia je na nasze {187]}. Ta nazwa jest nie tylko inna - patrząc (origin/master), ale dosłownie nie może być taki sam jak każdy z naszych oddziałów. Jeśli utworzymy gałąź o nazwie origin/master,5 w przeciwieństwie do innych gałęzi, które nie posiadają tej samej nazwy. Tylko wtedy, gdy Git używa krótszej nazwy, mamy jedną (regularną, lokalną) gałąź o nazwie origin/master i inną (zdalnie śledzącą) gałąź o nazwie origin/master. (To jest jak bycie w grupie, w której każdy ma na imię Bruce {224]}.)

Tagi nie przechodzą przez to wszystko. Znacznik v2.1 jest po prostu nazwany refs/tags/v2.1. Oznacza to, że nie ma możliwości oddzielenia znacznika "ich" od znacznika "Twój". Możesz mieć swoją metkę lub ich metkę. Tak długo, jak nikt nigdy nie przesunie znacznika, nie ma to znaczenia: jeśli oba znaczniki mają ten znacznik, musi on wskazywać na ten sam obiekt . (Jeśli ktoś zacznie przenosić znaczniki, robi się nieprzyjemnie.)

W każdym przypadku, Git implementuje "normalne" pobieranie tagów za pomocą prostej reguły:6gdy Git już ma commit, jeśli jakiś znacznik nazywa ten commit, Git również kopiuje znacznik. w przypadku zwykłych klonów, pierwszy klon pobiera wszystkie znaczniki, a następnie kolejne operacje git fetch otrzymują nowe znaczniki . Płytki klon, jednak z definicji pomija niektóre commity, a mianowicie wszystko poniżej dowolnego punktu grafu na wykresie. Te commity nie podniosą znaczników. They can ' t : aby mieć znaczniki, musisz mieć commity. Git nie jest dozwolony (z wyjątkiem płytkich grafts), aby mieć identyfikator commita bez jego posiadania.


3możesz podać git fetch niektóre refspec(y) w wierszu poleceń, a te nadpiszą domyślną wartość. Dotyczy to tylko domyślnego pobierania. Można również użyć wielu linii fetch = w konfiguracji, np. do pobrania tylko określonego zestawu gałęzi, chociaż normalnym sposobem na "ograniczenie" klonu początkowo z jedną gałęzią jest odłożenie zwykłego +refs/heads/*:refs/remotes/origin/* fetch Kolejka

4ponieważ znaczniki nie są przypuszczane do przeniesienia, możemy po prostu powiedzieć "to nic nie robi". Jeśli jednak się poruszają, + W refspec reprezentuje flagę siły, więc tag kończy się w ruchu.

5nie rób tego. To zagmatwane. Git poradzi sobie z tym dobrze-lokalna gałąź znajduje się w lokalnej przestrzeni nazw, a gałąź zdalnego śledzenia jest w zdalnej przestrzeni nazw-ale to naprawdę mylące.

6ta reguła nie pasuje do dokumentacji. Testowałem na Git w wersji 2.10.1; starsze Gits mogą używać innej metody.

 35
Author: torek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:38

Na temat samego procesu aktualizacji shallow clone, zobacz commit 649b0c3 formularz Git 2.12 (Q1 2017).
Ten commit jest częścią:

Commit 649b0c3, / align = "left" / f2386c6, commit 6bc3d8c, commit 0afd307 (06 Dec 2016) by Nguyễn Thái Ngọc Duy (pclouds). Zobacz commit 1127b3c, commit 381aa8e (06 Dec 2016) by Rasmus Villemoes (ravi-prevas). (połączone przez Junio C Hamano -- gitster -- w commit 3c9979b , 21 gru 2016)

shallow.c

Ten paint_down()jest częścią kroku 6 58babff (shallow.c: 8 kroków aby wybrać nowe commity dla .git / shallow - 2013-12-05) .
Kiedy pobieramy z płytkiego repozytorium, musimy wiedzieć, czy jeden z nowych/zaktualizowanych refów potrzebuje nowych "płytkich commitów" w .git/shallow (ponieważ nie mamy wystarczającej historii tych refów) i który z nich.

Pytanie w kroku 6 brzmi, co (nowe) płytkie commity są wymagane w inne, aby utrzymać osiągalność w całym repozytorium BEZ skracamy naszą historię?
Aby odpowiedzieć, oznaczamy wszystkie commity osiągalne z istniejących referencji nieciekawymi ("rev-list --not --all"), Oznacz płytkie commity dnem, a następnie dla każdego nowego/zaktualizowanego ref-a, przejdź przez wykres commitów, aż trafimy albo na nieciekawe, albo na dół, zaznaczając ref na commicie, gdy przechodzimy.

Po zakończeniu chodzenia sprawdzamy nowe płytkie commity. Jeśli my nie widział każdy nowy ref oznaczony na nowym płytkim commicie, wiemy wszystko nowe / zaktualizowane refy są dostępne tylko za pomocą naszej historii i .git/shallow.
Płytkie zobowiązanie, o którym mowa, nie jest potrzebne i można je wyrzucić.

Więc, kod.

Pętla tutaj (aby przejść przez commity) to w zasadzie:

  1. pobierz jeden commit z kolejki
  2. ignoruj, jeśli jest widoczny lub nieciekawy
  3. zaznacz to
  4. Przejrzyj wszystkich rodziców i..
    • 5.a a mark it if it ' s never marked before
    • 5.b put it back in the queue

W tym patchu robimy krok 5A, ponieważ nie jest konieczne.
Commit oznaczony na 5a jest umieszczany z powrotem w kolejce, a zostaną oznaczone w kroku 3 przy następnej iteracji. Jedynym przypadkiem będzie nie być zaznaczone jest wtedy, gdy commit jest już oznaczony jako nieciekawy (5a nie zaznacza tego), które zostaną zignorowane w Kroku 2.

 3
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-28 20:32:37