Skip to content

Instantly share code, notes, and snippets.

@szajbus
Last active April 20, 2016 09:26
Show Gist options
  • Save szajbus/077ed24c1256d796b601a40f4ed90c9b to your computer and use it in GitHub Desktop.
Save szajbus/077ed24c1256d796b601a40f4ed90c9b to your computer and use it in GitHub Desktop.

CLI tricks

Git

Po pierwsze must-have alias w systemie (~/.bashrc lub ~/.zshrc)

alias g="git"

Kolejny must-have to completions do gita (google: "git [bash|zsh] completions")

Pozwala to TAB-em uzupełniać nazwy subkomend, parametrów, remote'ów, branchy itd, podobnie jak TAB-em uzupełnia się nazwy plików i katalogów w konsoli.

Kilka opcjonalnych aliasów, przyspieszających najczęstsze akcje

alias gp="git push"
alias gpl="git pull"
alias gs="git status"
alias gd="git diff"
alias gf="git fetch"

Przyspiesza to pracę

gp p master
gf p
gd Gemfile

Bardziej rozbudowane aliasy można zdefiniować w ~/.gitconfig, najbardziej wg mnie przydatne:

ci = commit
co = checkout

br = branch

nb = checkout -b   # create new branch
remote = remote -v # show remotes verbosely by default

hard = reset --hard       # reset all changes
undo = reset --soft HEAD^ # go back before last commit, with files in uncommitted state

append = commit --amend -C HEAD   # append changes to previous commit

Szczególnie git append to dla mnie duży time-saver, bo często pracując nad daną funkcjonalnością tworzę commit, a potem jeszcze testuję kod lokalnie lub na stagingu. Zamiast ewentualne fixy dodawać jako kolejne commity i robić w ten sposób bałagan w historii robię po prostu:

g add [files]
g append

W ten sposób dodaję kolejne zmiany do poprzedniego commita w bardzo szybki sposób (git nie pyta nawet o commit message).

Aby powrócić do poprzedniego brancha bez konieczności wpisywania jego często długiej nazwy warto korzystać z git checkout -, w którym - jest aliasem na ostatnio odwiedzonego brancha.

[master] g co feature/branch
[feature/branch] g co -
[master] # jesteśmy z powrotem na masterze
[master] g co -
[feature/branch] # i znowu na feature/branch'u

Zresztą wszystkie komendy gita, które działają na branchach obsługują -, np. git merge -, git rebase -.

Swoją drogą, systemowy cd - działa analogicznie.

Aby przejrzeć historię bez przechodzenia na githuba, bitbucketa lub inne wizualne narzędzie, służy git log. Domyślnie pokazuje on paginowaną listę wszystkich commitów w obecnym branchu.

Wyświetlenie tylko kilku ostatnich commitów

g log -5

Wyświetlenie commitów wraz z diff'ami (-p oznacza --patch)

g log -5 -p

Wyświetlenie historii zmian wybranego pliku

g log path/to/file

Można też zobaczyć historię zmian tylko dla wybranej części pliku, np. zakresu linii zawierającego definicję wybranej metody. Git radzi sobie tu nawet gdy wybrany fragment pliku w różnych momentach przesuwał się w górę lub dół (gdy dodawano lub usuwano linie ponad nim), kurczył lub rozszerzał (gdy dodawano lub usuwano linie w środku bloku).

Np. gdy chcemy zobaczyć jak zmieniały się linie 10-20 w modelu User:

g log -L 10,20:app/models/user.rb

Aby zobaczyć lub odtworzyć starą wersję danego pliku należy podać sha tej rewizji:

g show ff1267ec2:path/to/file # printuje na stdout
g show ff1267ec2:path/to/file > new_file # printuje do pliku

W wyjątkowych przypadkach może się okazać, że chcemy odzyskać plik lub commit, którego nie ma już w żadnym branchu, np. gdy skasowaliśmy branch lub sam commit. Jest to możliwe, ponieważ git w swojej bazie trackuje wszystkie kiedykolwiek utworzone obiekty (commity, stash'e itd) oraz historię akcji, które powodują zmianę rewizji/sha (np. checkout, pull). Dzięki temu można się cofać w czasie, a także wyciągać te obiekty z historii.

Wyświetlenie historii wykonuje się przez git reflog. Otrzymując w ten sposób sha interesujących nas rewizji, możemy np. dostać się do indywidualnych plików przez git show lub wyciągnąć np. cały stary commit do obecnego brancha przez git cherry-pick [sha]

Mergowanie

Oczywista oczywistość - warto tworzyć feature branche przy rozwijaniu większych funkcjonalności.

Branche za pierwszym razem pushujemy do remote'a z opcją -u (lub --set-upstream) co automatycznie ustawia tracking pomiędzy lokalnym i zdalnym branchem, dzięki czemu potem nie trzeba explicite podawać nazwy remote'a i brancha przy pullowaniu i pushowaniu.

# Pierwszy push z ustawieniem trackowania
[feature/branch] g push -u origin feature/branch 

# Potem wystarczy tylko
[feature/branch] g push
[feature/branch] g pull

Mergowanie feature brancha do mastera powinno się robić jako non-fast-forward (--no-ff), nawet jeśli od punktu odgałęzienia od mastera nie doszły do niego żadne nowe commity. Dzięki temu po pierwsze tworzy się merge-commit i w historii jasno widać co się działo, a po drugie w razie potrzeby wycofania całej funkcjonalności wystarczy zrevertować ten merge-commit.

Można takie zachowanie ustawić na sztywno w gitconfig'u. W tym przypadku taka strategia mergowania jest ustawiona dla brancha master i staging.

[branch "master"]
  mergeoptions = --no-ff --no-edit
[branch "staging"]
  mergeoptions = --no-ff --no-edit

Z kolei podczas mergowania lokalnego brancha ze zmianami z remote'a (czyli np. przy git pull) należy merge-commitów unikać, bo tworzą one tylko bałagan w historii. Tu najlepiej dbać o możliwie liniową historię commitów. Czyli tutaj idealnie powinniśmy używać strategii fast-forward (--ff), jednak nie zawsze jest to możliwe - konkretnie w przypadku gdy nasz lokalny branch zawiera commity, których nie spushowaliśmy do remote'a oraz remote zawiera commity, których nie jeszcze spullowaliśmy. Pozostaje nam rebase, czyli nałożenie naszych lokalnych zmian na zmiany z remote'a, aby zachować liniową historię.

[master] g rebase origin/master

Lub szybciej

[master] g pull --rebase

Lub jeszcze szybciej - ustawiając w gitconfigu, aby domyślnie mergował z remote branchem lub rebase'ował.

[branch]
  autosetupmerge = true
  autosetuprebase = always

Wtedy zawsze możemy standardowo korzystać po prostu z git pull i nie zastanawiać się co się dzieje pod spodem.

Małym minusem jest to, że trzeba umieć sobie poradzić kiedy podczas rebase pojawią się konflikty.

Będąc przy temacie konfliktów - czasem zdarza się, że te same konflikty musimy rozwiązać wielokrotnie (np. przy mergowaniu zmian do kilku branchy lub ponawiając wcześniej przerwany rebase). Git potrafi zapamiętać już raz rozwiązane konflikty i potem automatycznie je rozwiązywać samemu. Wystarczy w gitconfigu włączyć rerere:

[rerere]
  # Remember my merges
  # http://gitfu.wordpress.com/2008/04/20/git-rerere-rereremember-what-you-did-last-time/
  enabled = true

Heroku

Polecam nazywać remote'y heroku krótkimi nazwami, wskazującymi na środowisko aplikacji.

git remote add p git@heroku.com:production-app.git
git remote add s git@heroku.com:staging-app.git

Dzięki temu potem można szybciej wykonywać komendy oraz deployować kod.

heroku run console -r p
git push p master

Ruby

Aby podejrzeć kod źródłowy gema, używanego w aplikacji

bundle open [gem]

Gem otwierany jest w domyślnie ustawionym w systemie edytorze (przez zmienną $EDITOR), ale można też sobie to nadpisać przez ustawienie zmiennej $BUNDLER_EDITOR. Np. export BUNDLER_EDITOR="atom".

Inne

Pretty-formatowanie JSON'a w konsoli przez stworzenie aliasu w ~/.bashrc lub ~/.zshrc:

alias jsonp="ruby -r json -e 'puts JSON.pretty_generate(JSON.parse(readlines.join))'"

Można go potem używać jak standardowej unixowej komendy, która działa na stdin/stdout tak jak powinna:

curl api.com/file.json | jsonp
jsonp < file.json > pretty_file.json
@rdk08
Copy link

rdk08 commented Apr 20, 2016

Dobrym pomysłem jest też stworzenie sobie pliku z różnymi komendami (Unix / Git / Rails / Postgres), które np. trudno zapamiętać i zaaliasować sobie ten plik w taki sposób:

alias c='less ~/.commands'
alias c!='vim ~/.commands'

Dzięki temu jedną literą w command linie jesteśmy w stanie przejść do zapisanych komend.

Wyszukiwanie komend można robić w less-ie przez / albo w shellu przez np. c | grep <string>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment