EnglishPolski
Najprostszy sposób na zarządzanie dotfilesami
Linux

Najprostszy sposób na zarządzanie dotfilesami

Ludzie dzielą się na tych co robią backupy, i na tych co dopiero zaczną je robić. Oczywiście, nie życzę nikomu problemów z utratą danych, ale jeżeli już trzeba faktycznie zaczynać od początku, to dobrze mieć możliwośc szybkiego przywrócenia swojego codzinnego workflow.

A ponieważ workflow w dużej mierze opiera się na dotfiles, to dobrze mieć możliwość ich łatwego backupowania, a przy okazji także możliwość synchronizacji między maszynami na których codziennie pracujemy.

Oczywiści istnieją dedykowane narzędzia, warto tutaj wymienić choćby GNU Stow, który działa na zasadzie tworzenia linków symbolicznych. Ale z gita korzystam tak czy inaczej. Więc po co komplikować?

Bare repo

Bare repo to klucz do całego rozwiązania.

Jeżeli poczytamy dokumentację gita, znajdziemy tam informację, że bare repo służy przede wszystkim do synchronizacji i udostępniania kodu między maszynami, a nie do bezpośredniej pracy nad tym kodem.

Czyli dokładnie to co jest potrzebne.

Dla przypomnienia - standardowe repozytorium gita, składa się z katalogu .git, zwierającego całą historię zmian, oraz katalogu roboczego w którym znajduje się aktualna rewizja kodu.

Bare repo to repozytorium zawierające tylko strukturę katalogu .git. Czyli krótko podsumowując - bare repo to czysta historia zmian, bez żadnych dodatkowych elementów.

Jeżeli dodamy do tego katalog domowy jako katalog roboczy, to wychodzi z tego idealne rozwiązanie.

Repozytorium w katalogu domowym

Więc tak - katalog domowy to nasz katalog roboczy dla repozytorium (worktree).

Utwórzmy więc nowe repozytorium:

git init --bare $HOME/.dotfiles

Pierwsze pytanie które bym zadał po zobaczeniu ww. polecenia, to dlaczego ten katalog ma się nazywać .dotfiles, a nie .git?

Jeżeli korzystasz ze zmodyfikowanego prompta, np. ze względu na motyw terminala, czy użycie OHMYZSH to użycie nazwy .git spwoduje, że prompt pokaże informację o aktualnym branchu. A ja tego nie chcę.

Oczywiście nazwa może być zupełnie inna, ale warto ten plik ustawić jako ukryty (dotfile) żeby przypadkiem nie go nie zmodyfikować.

Jeżeli przejrzymy teraz zawartość tego katalogu:

ls $HOME/.dotfiles
COMMIT_EDITMSG  HEAD  branches  config  description  hooks  index  info  logs  objects  refs 

To zobaczymy standardową strukturę katalogu .git. I tak właśnie ma być.

Ale jak spróbuje skorzystać z git status w katalogu domowym:

git status

Błąd:

fatal: not a git repository (or any of the parent directories): .git

Nie działa. To dlatego, że git szuka katalogu .git w katalogu domowym, a tam go nie ma. Dokładnie tak jak chciałem. Na szczęście, git pozwala na określenie katalogu repozytorium, jak również samego worktree za pomocą parametrów --git-dir i --work-tree.

I tego właśnie chcemy użyć. Warto od razu zdefiniować też alias, żeby nie musieć ciągle wpisywać tych parametrów:

alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'

I już. Trzeba tylko pamiętać, że od tej pory, zamiast git, trzeba używać dot przy odwoływaniu się do repozytorum dla dotfilesów:

dot status

Ostatnia rzecz, jaką koniecznie trzeba zrobić, to utworzyć sobie zdalne repozytorium, np. na GitHubie. Pamiętaj tylko, żeby dodać repozytorium jak prywatne. I nie polecam trzymać tam sekretów.

Ustawmy też branch main jako domyślny.

dot remote add origin <url_do_githuba>
dot branch -M main

Okej. To jest wersja absolutnie minimalna, ale już pozwoli na łatwe zarządzanie dotfilesami.

Dodajmy zatem najważniejszy plik:

dot add .zshrc
dot commit -m "initial commit - .zshrc"
dot push -u origin main

I już. Pierwszy .dotfile jest bezpieczny.

Ukrycie plików i zachowanie porządku

Uważam, że zarządznie dotfilesami jest ważne, więc warto dodać jeszcze kilka ulepszeń. Przede wszystkim, chcemy w tym repo trzymać tylko to co niezbędne, żadnych śmieci.

Żeby przypadkiem nie dodać żadnego nieporządanego pliku, ustawmy konfigurację repozytorium tak, żeby domyślnie wszystkie pliki były ignorowane:

dot config --local status.showUntrackedFiles no

Dzięki temu, dot status pokaże tylko te pliki, które zostały dodane do repozytorium, a nie wszystkie nieśledzone pliki w katalogu domowym.

Oczywiście, jeżeli chcemy dodać jakiś nowy plik, to musimy go ręcznie dodać do repozytorium (np. tutaj konfiguracja neovima):

dot add .config/nvim/init.vim
dot commit -m "add nvim config"
dot push

Jak nie zgubić zmian?

Warto też pamiętać, że jeżeli już używamy repo do trackowania zmian, to warto je od czasu do czasu commitować i wypychać na zdalne repo. Żeby o tym pamiętać, ustawiłem sobie taką przypominajkę w pliku .zshrc:

# check if dotfiles are committed
if [[ -n $(dot status --porcelain) ]]; then
    echo "⚠️ Dotfiles not committed ⚠️"
fi 

Dzięki temu, każde otwarcie sesji terminala spowoduje wyświetlenie komunikatu, jeżeli w dotfilesach są jakieś nie dodane zmiany. Wydaje mi się to najprostszym sposobem, żeby o tym pamiętać.

Oczywiście można byłoby wykorzystać np. crona do robienia automatycznych commitów, ale ww rozwiązanie wydaje mi się wystarczające.

Krótki timesaver

Dzięki poniższemu aliasowi, jeżeli wpiszę dot bez żadnych parametrów, od razu zobaczę status repozytorium. A przekazując jakieś polecenie, np. dot add .zshrc, to zostanie ono wykonane bezpośrednio:

alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME "${@:-status}"'

Synchronizacja

Dobra, to skoro mamy już pliki w repo, to teraz jak je zsynchronizować na nową maszynę? Wystarczy sklonować repozytorium do katalogu domowego, ale z parametrem --bare:

git clone --bare <url_do_githuba> $HOME/.dotfiles

Następnie, podobnie jak wcześniej, ustawiamy alias:

alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME "${@:-status}"'

I ostatni krok - checkout:

dot checkout --force

Dlaczego --force?

Jeżeli w katalogu domowym mamy jakieś pliki (a zapewne tak jest), git nie pozwoli na zrobienie checkouta gdyż pliki zostaną nadpisane. Ale ponieważ wiemy, że chcemy nadpisać te pliki, to używamy --force i git zrobi checkout bez żadnych pytań.

Gotowe.

Oczywiście warto na drugiej maszynie też ustawić ukrycie nieśledzonych plików. Pozostało już tylko commitowanie zmian i wypychanie ich na zdalne repo. Oraz regularne używanie dot pull --rebase żeby zawsze mieć aktualną wersje plików.

Podsumowanie

Korzystam z tego rozwiązania już dość długo, żeby mieć pewność jego działania. Jest proste, szybkie i wymaga tylko tego co już i tak jest używane w innych projektach - skonfigurowanego gita. Żadnych dodatkowych narzędzi, zero komplikacji.

Prostota jest szczytem wyrafinowania. - Leonardo da Vinci

Back to Top