Git_2

Git – podstawowe narzędzie pracy dewelopera i nie tylko. Część II: trochę komend i trochę praktyki

W związku z dużym zainteresowaniem poprzednim artykułem o Gicie postanowiłem umieścić na naszym blogu kilka komend/praktyk/instrukcji, które w mojej pracy z tym narzędziem okazały się bardzo przydatne lub zostały przeze mnie uznane za ciekawe.

W związku z dużym zainteresowaniem poprzednim artykułem o Gicie postanowiłem umieścić na naszym blogu kilka komend/praktyk/instrukcji, które w mojej pracy z tym narzędziem okazały się bardzo przydatne lub zostały przeze mnie uznane za ciekawe.

Artykuł zakłada, że osoba czytająca posiada przynajmniej minimalną wiedzę na temat Gita. Niektóre z zagadnień będą jednak na odrobinę wyższym poziomie :) Przydatna może okazać się także wiedza na temat programowania w powłoce BASH i o szeroko pojętym Linuksie.

Instalacja ze źródeł – czyli najnowsza wersja Gita

Czasami nachodzi nas chęć posiadania najnowszej wersji Gita. Potencjalnych motywów tej zachcianki może być wiele:

  1. Nowsze wersje zawierają funkcjonalność, która jest dla nas atrakcyjna/niezbędna.
  2. Software, który chcemy zainstalować, posiada dependencje do nowszej wersji Gita.
  3. Wspaniałe uczucie bycia na krwawiącej krawędzi (bleeding edge) ;)

Do ostatniego podpunktu chciałbym dorzucić mały komentarz: trudno, żeby wersja rozwojowa, a tym bardziej ostatnia stabilna tak dojrzałego oprogramowania, jakim jest Git, niosła ze sobą niewyobrażalne ryzyka. Niestety Git jako projekt nie korzysta z oprogramowania do obsługi zgłoszeń (Issue Tracking Software). Błędy zgłaszane są na listę mailingową. Wyciągnięcie danych dotyczących błędów oraz ich rzeczywistego wpływu, typu błędu itp. byłoby bardzo czasochłonne i pozwolę sobie je pominąć.

By sprawdzić najnowszą stabilną wersję Gita możemy wejść na stronę https://git-scm.com/downloads
Wybieramy link Linux, by potem kliknąć „download a tarball”. Przeniesie on nas na stronę https://www.kernel.org/pub/software/scm/git/ skąd pobieramy zadany numer wersji. Następnie instalujemy niezbędne pakiety, by potem skompilować naszą wersję Gita.

Cały proces jest prosty – jednak w przypadku wykonania go na dużych środowiskach, szczególnie chmurowych (małe maszyny, minimalna ilość zainstalowanych pakietów), może być po prostu żmudny. Poniżej przedstawiam prosty skrypt, który automatyzuje cały proces. Licencje MIT zamieszczam tylko w celach uniknięcia jakichkolwiek niedomówień.

#!/bin/bash
# The MIT License (MIT)
# Copyright (c) 2017 EuroLinux
# Author: Alex Baranowski
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

MY_LOGFILE="/var/log/git_installation.log"
MY_REQUIRED_COMMAND='yum tee sed wget'

check_running_as_root(){
    if [ "$EUID" -ne 0 ]; then
        echo "This script requires root privilages. Please run it as root or via sudo."
        exit 1
    fi
}
check_required_commands(){
    for my_command in $REQUIRED_COMMAND; do
        hash $my_command 2>/dev/null || { echo "Script requires $my_command command! Aborting."; exit 1; }
    done
}

rpm_mods(){
  echo "Removing git rpm if installed, install necessary rpm packages." | tee $MY_LOGFILE
  yum remove -y git
  yum install -y curl-devel expat-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker gettext
}

get_newest_stable_git_version(){
    # NOTE: reading tags with regex is the simplest but definitely not the best way
    page="https://git-scm.com/downloads"
    tag_open="span class=\"version\""
    tag_end="span"
    git_current_v=$(curl -s $page | sed -n "/<$tag_open>/,/<\/$tag_end>/p" | grep -Eo '[0-9\.]*')
    echo "Getting the newest git git-${git_current_v}" | tee $MY_LOGFILE

    echo "Getting git-${git_current_v} and unpacking it into /usr/src/" | tee $MY_LOGFILE
    wget https://www.kernel.org/pub/software/scm/git/git-${git_current_v}.tar.gz -O /tmp/git-latest.tar.gz
    tar xvzf /tmp/git-latest.tar.gz -C /usr/src/
    cd /usr/src/git-${git_current_v}/
    ./configure
    make
    make install
}

set -e # Script stops on first error
check_running_as_root
check_required_commands
rpm_mods
get_newest_stable_git_version

Skrypt był testowany na minimalnych obrazach stworzonych dla chmur, które są mniejsze niż instalacja minimalna EuroLinuksa 6 i 7. Oczywiście zgodnie z licencją każdy może z niego korzystać, zmieniać, a nawet zamknąć jego kod źródłowy :) Z tego powodu licencja MIT jest uznawana za bardziej liberalną niż GPL.

Puste katalogi w Gicie

Nie każdy o tym wie, ale Git nie trzyma pustych katalogów. Zdarza się jednak, iż puste katalogi są nam potrzebne. Osobiście odkryłem ten fakt podczas pracy przy projekcie studenckim, gdy utworzyłem w repozytorium pusty katalog, który w trakcie budowania programu był wypełniany różnymi plikami. Pliki te finalnie były używane w końcowym programie. Po przetestowaniu procesu i wysłaniu na projektowe repozytorium, zadowolony udałem się na zasłużony spoczynek. Jakież było moje zdziwienie, gdy rano odebrałem telefon, że obrazki się nie ładują, a pokaz naszej pracy miał odbyć się za 2 godziny. Na szczęście po wyrażeniu „nie ładują”, „nie działa” moja głowa przygotowała nieszablonową i błyskotliwą odpowiedź – „Jak to? U mnie działa”.

Nie zanudzając, podaję dwa skrypty, które tworzą/usuwają puste pliki .gitkeep. W podanej wersji służą mi już od dobrych kilku lat.

#!/bin/bash
#This script will place .gitkeep files in empty directories
if [[ $# -eq 0 || $1 == '--help' || $1 == '-h' ]]
then
    echo "USAGE: $0 [dir]"
    exit 1
fi
echo "Placing .gitkeep in empty directories"
for i in $(find $1 -type d -empty -print);do echo "$i/.gitkeep"; touch $i/.gitkeep; done
#!/bin/bash
#This script will remove .gitkeep files from dir
if [[ $# -eq 0 || $1 == '--help' || $1 == '-h' ]]
then
    echo "USAGE: $0 [dir]"
    exit 1
fi
echo "Removing .gitkeep in empty directories"
for i in $(find $1 -type f -empty -print | grep .gitkeep);do echo "removing $i"; rm -f $i; done

Aliasy w Gicie

Alias to wykonanie komendy przy pomocy jej skrótu. W większości dystrybucji Linuksowych możemy wpisać ll. Jest to alias komendy ls. Dla przykładu w EuroLinuksie w wersji 7 ll jest aliasem zdefiniowanym w /etc/profile.d/colorls.sh, a za samo aliasowanie odpowiada linia o treści alias ll='ls -l' 2>/dev/null lub alias ll='ls -l --color=auto' 2>/dev/null w zależności od ustawień zmiennej USER_LS_COLORS.

W Gicie możemy także tworzyć swoje aliasy, zarówno z linii komend, jak i pliku konfiguracyjnego. O plikach konfiguracyjnych i ich kolejności pisałem w poprzedniej części.

Przykładowe aliasy z pliku ~/.gitconfig

[alias]
    a = add
    b = branch
    c = commit

Aliasy możemy też konfigurować z linii poleceń. By dodać aliasy, używamy komendy git config.

git config --global alias.a add
git config --global alias.b branch
git config --global alias.c commit

Zauważmy, że w przypadku wieloargumentowych aliasów musimy je ująć w ‘ ‘ lub ” “, by z punktu widzenia programu były jednym spójnym argumentem.

Na sam koniec akapitu przedstawię 2 aliasy, z których sam nieprzerwanie korzystam.

git config --global alias.ci "commit --interactive"
git config --global alias.s "status -bs"

Pierwszy z nich to git ci, czyli interaktywny commit. Pozwala on interaktywnie wybrać pliki, które znajdą się w naszym commicie. Zauważmy jednak, że przy dużej ilości plików narzędzie staje się bezużyteczne.

Przykładowo tworzymy 100 plików tzw. jednolinijkowcem for i in {1..100}; do echo $i > $i; done. Używając teraz interaktywnego commitowania, nie będziemy w stanie wylistować plików, które możemy dodawać. Dla przypomnienia, za standardowy rozmiar terminala uważa się 80 × 24, czyli 80 znaków szerokości w 24 liniach. Wylistowanie plików zajmie w takim razie ponad 4 terminale. Jest to problem, jeśli nie mamy terminala, w którym nie możemy przewijać wyświetlanego tekstu. Nawet jeśli możemy dokonać takiego zabiegu, w dłuższej perspektywie jest to irytujące. Omawiany przykład ilustruje poniższy listning.

[~/t/gnome-pomodoro]─[⎇ gnome-3.14]─[±]─> git ci
           staged    unstaged path

*** Commands ***
  1: [s]tatus 2: [u]pdate 3: [r]evert 4: [a]dd untracked
  5: [p]atch 6: [d]iff 7: [q]uit 8: [h]elp
What now> 4
******************* Niewidoczne Linie ***************************
 78: 79
 79: 8
 80: 80
 81: 81
 82: 82
 83: 83
 84: 84
 85: 85
 86: 86
 87: 87
 88: 88
 89: 89
 90: 9
 91: 90
 92: 91
 93: 92
 94: 93
 95: 94
 96: 95
 97: 96
 98: 97
 99: 98
 100: 99
Add untracked>>

Podsumowując interaktywne commitowanie, przy niewątpliwych zaletach słabo radzi sobie z dużą ilością nowych/zmienionych plików.

Drugi alias do statusu jest uruchomieniem komendy status z przełącznikami -b (wyświetla krótką informację o gałęzi i jej statusie) -s (wyświetla wyjście w krótkim formacie). Porównajmy sobie teraz informacje uzyskane przy pomocy aliasu git s i zwykłego git status.

[~/t/gnome-pomodoro]─[⎇ gnome-3.14]─[±]─> git s
## gnome-3.14...origin/gnome-3.14 [ahead 1]
 D README.md
 D autogen.sh
 M config.sub
A new_added.file
?? new.file

[~/t/gnome-pomodoro]─[⎇ gnome-3.14]─[±]─> git status
# On branch gnome-3.14
# Your branch is ahead of 'origin/gnome-3.14' by 1 commit.
#   (use "git push" to publish your local commits)
#
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#    new file: new_added.file
#
# Changes not staged for commit:
#   (use "git add/rm ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#    deleted: README.md
#    deleted: autogen.sh
#    modified: config.sub
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#   new.file

To, które wyjście cechuje się większą czytelnością, pozostawiam ocenie czytelników.
W przypadku aliasów często używanych komend, w tym wypadku git status, chcę zwrócić uwagę na istotne fakty:

  1. „Status” to 6 liter, a „s” to tylko 1 litera.
  2. Średnią prędkość pisania na klawiaturze przez programistów określa się na 40 słów na minutę. Słowo jest liczone jako 5 liter + spacja, czyli 6 znaków. Wychodzi nam więc, że jeden znak kosztuje 60/(40 * 6), czyli równe 0.25 sekundy. Różnica między więc między „git s” a „git status” to 1.25 sekundy.
  3. Programista sprawdza status repozytorium gitowego przy pisaniu przynajmniej 10 razy dziennie. Daje nam to 12.5 sekund dziennie, czyli w skali roku – liczmy dla całego roku, bo programista programuje nawet na wakacjach – otrzymujemy 5*(0.25)*10*365/60 = 76 MINUT, czyli dużo ponad godzina! Czas ten zamiast na pisaniu tatus moglibyśmy spędzić w łóżku, by choć raz w roku lepiej się wyspać :).

Na koniec proponuję mały eksperyment. Co się stanie, jeżeli dodamy alias do istniejącej komendy Gita? Co, jeśli dodamy rekursywny alias? Jak zmienią się pliki konfiguracyjne?

git config --global alias.add "status -bs"
git config --global alias.status "status -bs"

Informacja o Gicie w wierszu poleceń

Prompt to informacja, którą podaje nam konsola. Osobiście od dość dawna nie korzystam ze standardowej konsoli (BASH) na rzecz dużo nowszej, szybszej i bardziej przyjaznej – fish. Konsola ta w niedługim czasie na 100% zagości na blogu EuroLinuksa. Pozwolę sobie teraz pokazać, jak przyjaźnie pokazuje ona gałąź, ale także status, w jakim jest Git.

[~/w/E/ssllabs-scan]─[⎇ stable]─> # Wygląd bez dodanego pliku, gałąź stable
[~/w/E/ssllabs-scan]─[⎇ stable]─[±]─> # Wygląd z nowym plikiem, ta sama gałąź bez commitu'u
[~/w/E/ssllabs-scan]─[⎇ stable]─> git checkout master
Branch master set up to track remote branch master from origin.
Switched to a new branch 'master'
[~/w/E/ssllabs-scan]─[⎇ master]─>

Oczywiście istnieje wiele tematów (sposobów wyświetlania prompta) i podany powyżej jest tylko jednym z nich.

Osobom, które nie chcą zmieniać jednak swojej powłoki (bash), proponuję użyć przyjaznego narzędzia do generowania własnego prompta – http://ezprompt.net/. Narzędzie to posiada także sekcję związaną z obecnością Gita. Oprócz tego można także znaleźć otwarte wtyczki/tematy/skrypty do wypisywania informacji o Gicie w prompcie. Szukać ich należy między innymi w serwisie github.com

Osobiście zdarzyło mi się skorzystać z https://github.com/magicmonty/bash-git-prompt. Rozwiązanie było jednak dosyć powolne. Było to jednak ponad 2 lata temu i od tego czasu wiele mogło się zmienić.

Podsumowanie i zapowiedź

Git jest rozbudowanym i potężnym narzędziem. W związku z tym opis tego, dlaczego i jak go używać jest tematem rzeką. Mam nadzieję, że Czytelnik znajdzie w artykule coś interesującego dla siebie.

W kolejnym artykule z tej serii znajdą się między innymi informacje o git bisect, git blame i git-flow.

Autorzy

Artykuły na blogu są pisane przez osoby z zespołu EuroLinux. 80% treści zawdzięczamy naszym developerom, pozostałą część przygotowuje dział sprzedaży lub marketingu. Dokładamy starań, żeby treści były jak najlepsze merytorycznie i językowo, ale nie jesteśmy nieomylni. Jeśli zauważysz coś wartego poprawienia lub wyjaśnienia, będziemy wdzięczni za wiadomość.