Jak wyodrębnić podkatalog git i zrobić z niego podmoduł?

119

Zacząłem projekt kilka miesięcy temu i zapisałem wszystko w głównym katalogu. W moim głównym katalogu "Project" jest kilka podkatalogów zawierających różne rzeczy: Project / paper zawiera dokument napisany w LaTeX Project / sourcecode / RailsApp zawiera moją aplikację railsową.

"Projekt" jest oznaczony GIT i było wiele zatwierdzeń zarówno w katalogu "paper", jak i "RailsApp". Teraz, ponieważ chciałbym użyć cruisecontrol.rb dla mojej „RailsApp”, zastanawiam się, czy istnieje sposób na utworzenie podmodułu z „RailsApp” bez utraty historii.

Cœur
źródło
2
Również bardzo dobra odpowiedź: stackoverflow.com/questions/359424/…
Rehno Lindeque

Odpowiedzi:

122

W dzisiejszych czasach istnieje znacznie łatwiejszy sposób na zrobienie tego niż ręcznie za pomocą git filter-branch: git subtree

Instalacja

UWAGA git-subtree jest teraz częścią git(jeśli zainstalujesz Contrib) od 1.7.11, więc możesz już mieć ją zainstalowaną. Możesz sprawdzić, wykonując git subtree.


Aby zainstalować poddrzewo git ze źródła (dla starszych wersji git):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

Lub jeśli chcesz strony podręcznika i wszystko

make doc
make install

Stosowanie

Podziel większy na mniejsze kawałki:

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add origin [email protected]:my-user/new-project.git
git pull ../ foo-only
git push origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add [email protected]:my-user/new-project.git foo

Aby uzyskać szczegółową dokumentację (strona podręcznika), przeczytaj git-subtree.txt.

apenwarr
źródło
10
git poddrzewo rządzi!
Simon Woodside,
3
Ale czy nie celem git-subtree nie jest unikanie używania podmodułów? Chodzi mi o to, że rzeczywiście jesteś autorem poddrzewa git (chyba że występuje kolizja pseudonimów), ale wygląda na to, że zmieniło się poddrzewo git, mimo że polecenie, które pokazujesz, wydaje się nadal aktualne. Czy dobrze to rozumiem?
Blaisorblade
17
git-subtree jest teraz częścią git (jeśli zainstalujesz contrib) od 1.7.11
Jeremy
8
Dobrze git rm -rf ./foousuwa fooz HEADale nie filtruje my-projectjest cała historia. Następnie git submodule add [email protected]:my-user/new-project.git footworzy tylko foopodmoduł zaczynający się od HEAD. Pod tym względem skrypty filter-branchsą lepsze, ponieważ pozwalają osiągnąć „rób tak, jakby subdir był od samego początku podmodułem”
Gregory Pakosz
thx za to - git subtree dokumentuje tylko trochę zaskakujące, a to jest (dla mnie) najbardziej użyteczna rzecz, jaką chciałem z tym zrobić ...
hwjp
38

Checkout git filter-branch .

ExamplesSekcja mężczyzny stronie pokazuje jak wyodrębnić podkatalogu do niego własnego projektu przy zachowaniu wszystkich jego historii i odrzucając historię innych plików / katalogów (tylko to, czego szukają).

Aby przepisać repozytorium tak, aby wyglądało, jakby foodir/było jego katalogiem głównym projektu, i odrzucić całą inną historię:

   git filter-branch --subdirectory-filter foodir -- --all

W ten sposób możesz np. Zamienić podkatalog biblioteki we własne repozytorium.
Zwróć uwagę na to, --że oddziela filter-branchopcje od opcji rewizji, oraz --allaby przepisać wszystkie gałęzie i tagi.

Pat Notz
źródło
1
To działało dobrze dla mnie. Jedynym minusem, jaki zauważyłem, było to, że wynikiem była jedna gałąź główna ze wszystkimi zatwierdzeniami.
aceofspades
@aceofspades: dlaczego to wada?
naught101
2
Dla mnie głównym celem wyodrębniania zatwierdzeń z repozytorium git jest to, że chcę zachować historię.
aceofspades
13

Jednym ze sposobów jest odwrotność - usuń wszystko oprócz pliku, który chcesz zachować.

Zasadniczo utwórz kopię repozytorium, a następnie użyj, git filter-branchaby usunąć wszystko oprócz plików / folderów, które chcesz zachować.

Na przykład mam projekt, z którego chcę wyodrębnić plik tvnamer.pydo nowego repozytorium:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

Wykorzystuje git filter-branch --tree-filterto przejście przez każde zatwierdzenie, uruchomienie polecenia i ponowne zatwierdzenie wynikowej zawartości katalogów. Jest to niezwykle destrukcyjne (więc powinieneś to robić tylko na kopii swojego repozytorium!) I może zająć trochę czasu (około 1 minuty na repozytorium z 300 zatwierdzeniami i około 20 plikami)

Powyższe polecenie po prostu uruchamia następujący skrypt powłoki dla każdej wersji, którą musiałbyś oczywiście zmodyfikować (aby wykluczyć twój podkatalog zamiast tvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

Największym oczywistym problemem jest to, że pozostawia wszystkie komunikaty o zmianach, nawet jeśli nie są one związane z pozostałym plikiem. Skrypt git-remove-empty-commits rozwiązuje ten problem.

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

Musisz użyć -fargumentu force run filter-branchponownie z czymkolwiek w refs/original/(co w zasadzie jest kopią zapasową)

Oczywiście to nigdy nie będzie idealne, na przykład jeśli twoje komunikaty o zmianach wspominają o innych plikach, ale jest to tak blisko, jak pozwala na to prąd git (o ile wiem).

Ponownie, uruchamiaj to tylko na kopii swojego repozytorium! - ale podsumowując, aby usunąć wszystkie pliki oprócz „thisismyfilename.txt”:

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'
dbr
źródło
4
git filter-branchma (obecnie?) wbudowaną opcję usuwania pustych zatwierdzeń, a mianowicie --prune-empty. Lepszym przewodnikiem git filter-branchpo odpowiedziach na to pytanie są: stackoverflow.com/questions/359424/ ...
Blaisorblade
4

Zarówno CoolAJ86 i apenwarr odpowiedzi są bardzo podobne. Chodziłem tam iz powrotem między nimi, próbując zrozumieć fragmenty, których brakowało w jednym z nich. Poniżej znajduje się ich połączenie.

Najpierw przejdź do Git Bash do katalogu głównego repozytorium git, które chcesz podzielić. W moim przykładzie to jest~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin [email protected]:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add [email protected]:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Poniżej znajduje się kopia powyższego z nazwami, które można dostosować, zastąpionymi i używającymi zamiast tego protokołu https. Katalog główny to teraz~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package
ShawnFeatherly
źródło
3

Jeśli chcesz przenieść jakiś podzbiór plików do nowego repozytorium, ale zachować historię, w zasadzie skończysz z zupełnie nową historią. Sposób, w jaki to działałoby, jest zasadniczo następujący:

  1. Utwórz nowe repozytorium.
  2. Dla każdej wersji starego repozytorium połącz zmiany w swoim module z nowym repozytorium. Spowoduje to utworzenie „kopii” istniejącej historii projektu.

Zautomatyzowanie tego powinno być dość proste, jeśli nie masz nic przeciwko napisaniu małego, ale włochatego skryptu. Proste, tak, ale także bolesne. Ludzie robili w przeszłości przepisywanie historii w Git, możesz to wyszukać.

Alternatywnie: sklonuj repozytorium i usuń papier w klonie, usuń aplikację z oryginału. Zajmie to minutę, na pewno zadziała i możesz wrócić do ważniejszych rzeczy niż próba oczyszczenia historii gita. I nie martw się o miejsce na dysku zajmowane przez nadmiarowe kopie historii.

Dietrich Epp
źródło