Udělej rozhodnutí a nauč se s ním žít

Dlouho jsem přemýšlel, co byly věci, které mě v životě hodně posunuly. Zlomové věci, které člověka raketově vyhodí dál v životě, práci atd. Byly to rozhodnutí, které jsem udělal.

Všichni v životě děláme rozhodnutí. A pak se s nimi musíme naučit žít. A nebo se nám to nedaří a věčně o nich přemítáme. Udělal jsem chybu? Mohl jsem se rozhodnout lépe? Mohl být můj dosavadní život lepší, kdybych se v dané situaci rozhodl jinak?

My lidé máme neobvyklou schopnost obhájit si svoje špatná rozhodnutí. Hlavně ta rozhodnutí, za která se stydíme. Najdeme si tisíc důvodů, proč vlastně nebyla tak špatná nebo proč byla správná. až tomu sami začneme věřit. Hitler by mohl vyprávět 🙂

Proč to ale je, že nejsme schopni sami sobě obhájit svá pozitivní nebo jen neutrální rozhodnutí? Proč jim nejsme schopni věřit a neustále o nich přemítáme? Proč nevěříme, že jsou ta nejlepší, které jsme v dané situaci mohli udělat?

Pokud se chceme v životě posunout dál a něco vybudovat, nesmíme ztrácet čas věčným přemítáním. Musíme začít věřit svým rozhodnutím a dělat další. Naučit se žít s rozhodnutími, která jsme již udělali, neboť ty už nezměníme.

Jak v Solaris / SmartOS zjistit sériové číslo diskového řadiče

V Solarisu se na to hodí nástroj prtpicl. Ten však zobrazí poměrně hodně textu. Je proto lepší si jej omezit např. programem egrep na výrobce vašeho řadiče takto:

prtpicl -v | egrep -i "lsi|sas"

Tento nástroj se také dá využít na zjištění informací prakticky o všem, co v systému je dostupné.

Zdroj: https://www.thegeekdiary.com/how-to-identify-the-hba-cardsports-and-wwn-in-solaris/

Jak nastavit správná práva PHP sessions složce

Složka /var/lib/php/sessions, kde se standardně ukládají session soubory PHP, má poněkud nezvyklá práva drwx-wx-wt. V Linuxu je nastavíme následovně:

$ chmod 1733 /var/lib/php/sessions

Pokud bychom chtěli složku snadno v jednom kroku prohodit za novou, prázdnou, vypadal by příkaz náledovně:

cd /var/lib/php && mkdir new && chmod 1733 new && mv sessions old && mv new sessions

Jak spustit dvě instance Redis v Ubuntu 16

Po přechodu Ubuntu na SystemD je práce se službami trochu složitější a návod pro SystemD jsem nikde nenašel. V tomto návodu si pro přehlednost přejmenujeme původní instanci Redisu s příponou dle jejího portu a přidáme druhou instanci s inkrementovaným portem.

Nejprve Redis odebere ze spuštění, aby nám nikde nestrašil. Redis dále poběží, instance budeme prohazovat až později.

$ systemctl disable redis-server.service
$ update-rc.d redis-server remove

Nyní nakopírujeme systemd definice našich nových instancí.

$ cd /etc/systemd/system
$ cp /lib/systemd/system/redis-server.service redis-server-6379.service
$ cp /lib/systemd/system/redis-server.service redis-server-6380.service

V definicích upravíme cesty ke konfiguraci a PID file a odebereme Alias.

[Service]
ExecStart=/usr/bin/redis-server /etc/redis/redis-6379.conf
PIDFile=/var/run/redis/redis-server-6379.pid

Nakopírujeme také konfiguraci Redisu.

$ cd /etc/redis
$ cp redis.conf redis-6379.conf
$ cp redis.conf redis-6380.conf
$ chown redis:redis redis-*.conf

Upravíme konfiguraci.

port 6379
dir /var/lib/redis/6379
pidfile /var/run/redis/redis-server-6379.pid
logfile /var/log/redis/redis-server-6379.log

Vytvoříme datové složky.

$ mkdir /var/lib/redis/6379
$ mkdir /var/lib/redis/6380
$ chown redis:redis /var/lib/redis/63*

Provedeme reload systemd a prohodíme instance.

$ systemctl daemon-reload
$ systemctl stop redis-server.service
$ mv /var/lib/redis/*.* /var/lib/redis/6379/
$ systemctl start redis-server-6379.service

Běží Redis?

$ redis-cli -p 6379 info server | egrep "process_id|tcp_port|config_file"

Přidáme novou instanci po spuštění systému a původní zakážeme spustit. To nám zabrání, aby se původní instance s nově pojmenovanou bila o port.

$ systemctl enable redis-server-6379.service
$ systemctl mask redis-server.service

Spustíme a povolíme druhou isntanci.

$ systemctl enable redis-server-6380.service
$ systemctl start redis-server-6380.service

Běží druhá instance?

$ redis-cli -p 6380 info server | egrep "process_id|tcp_port|config_file"

Ještě si porty v systému pojmenujeme pro lepší přehled v Netstat.

$ echo "redis1  6379/tcp" >> /etc/services 
$ echo "redis2  6380/tcp" >> /etc/services
$ netstat -l | grep redis

Jak inkrementovat IPv6 adresu v PHP

PHP nativně nepodporuje dostatečně velká (128 bit) čísla, aby bylo inkrementování decimální reprezentace IPv6 adresy (jednoduše) možné. Pro zjednodušení tedy budeme potřebovat GMP extension.

Nejdříve je potřeba převést IP adresu do binární podoby a instanciovat GMP číslo:

$ip = gmp_import(inet_pton('2001:15b::1'));

Poté můžeme IP inkrementovat tak, jak jsme zvyklí:

$ip++;

Nakonec IP převedeme zpět do klasického formátu:

$ip = inet_ntop(hex2bin(gmp_strval($ip, 16)));
// 2001:15b::2

Nebo pokud preferuje dlouhý formát adresy:

$ip = implode(':', str_split(gmp_strval($ip + 1, 16), 4));
// 2001:015b:0000:0000:0000:0000:0000:0002

Jak přesunout SmartOS image z jednoho serveru na druhý

Nedávno se mi stalo, že jsem potřeboval přesouvat SmartOS zónu z jednoho serveru na druhý. Zóna ale byla vyrobená z obrazu base-64-lts ve verzi 16.4.0. Tuhle verzi kvůli fatální chybě při upgradu stáhli z veřejného repozitáře.

Řešením by bylo dataset zóny povýšit pomocí zfs promote. Co když ale raději chceme origin zóny zachovat a přenést jej na nový server? Nástroj imgadm bohužel nic jako export neposkytuje. Naštěstí si ale můžeme poradit ručně.

Nejprve přeneseme dataset:

$ zfs send -Rep zones/08589eda-d64e-11e6-89a3-0b55371be927@final | ssh cilovy.server.tld zfs recv zones/08589eda-d64e-11e6-89a3-0b55371be927

Poté přesuneme konfiguraci pro imgadm:

$ scp /var/imgadm/images/zones-08589eda-d64e-11e6-89a3-0b55371be927.json cilovy.server.tld:/var/imgadm/images/

Nesmíme zapomenout na lock soubor, který imgadm používá, aby určil, zda je již daný image naimportovaný:

$ scp /var/run/img.08589eda-d64e-11e6-89a3-0b55371be927.import.lock cilovy.server.tld:/var/run/

A to je vše 🙂

Vlastní certifikační autorita pro přihlašování k SSH

Při přihlašování k SSH jste jistě již mockrát viděli hlášku, jako je tato:

The authenticity of host 'example.com (1.2.3.4)' can't be established.
RSA key fingerprint is 1b:9b:b8:5e:74:b1:31:19:35:48:48:ba:7d:d0:01:f5.
Are you sure you want to continue connecting (yes/no)?

Většinou ji napoprvé bezmyšlenkovitě potvrdíme a všímáme si jí pouze v případě, že se objeví znovu. Značí to, že je něco špatně. Jak se ale této hlášce vyhnout úplně? Ověřování fingerprintu jistě nechceme vypnout, to by nebylo moc bezpečné 🙂 Jak tedy na to jinak? Založíme si pro SSH vlastní CA!

Nejprve si musíme vygenerovat podepisovací klíč.

$ mkdir .ssh/ca
$ cd .ssh/ca
$ ssh-keygen -t ed25519 -C ca@example.com -f ca.ed25519
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ca.ed25519.
Your public key has been saved in ca.ed25519.pub.
The key fingerprint is:
SHA256:J3nD9rV49uE57+2w79GeMKw1+jfZeEcc6crNjFdfXeg ca@exmaple.com
The key's randomart image is:
+--[ED25519 256]--+
|                 |
|                 |
|               ..|
|         o    .o.|
|        S *  .o.+|
|         = o.oEoB|
|            +*@=B|
|            +**#@|
|           o..+X#|
+----[SHA256]-----+

Příkaz nám vygeneroval dva soubory ca.ed25519 a ca.ed25519.pub. Ukážu zde pouze generování pro algoritmus ED25519, pro jiné algoritmy je postup obdobný.

Nyní potřebujeme veřejný klíč serveru, ke kterému se budeme přihlašovat. Ten pak podepíšeme a podepsaný klíč nahrajeme zpět na server.

$ scp example.com:/etc/ssh/ssh_host_ed25519_key.pub .
$ ssh-keygen -s ca.ed25519 -I example.com -h -n example.com -V +156w ssh_host_ed25519_key.pub
Signed host key ssh_host_ed25519_key-cert.pub: id "example.com" serial 0 for example.com valid from 2017-03-03T17:57:00 to 2020-02-28T17:58:38
$ scp ssh_host_ed25519_key-cert.pub example.com:/etc/ssh/

Nyní bude potřeba říct o novém certifikátu SSH serveru. Přidáme do /etc/ssh/sshd_config jeden řádek a restartujeme SSH.

HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

Nyní můžeme na všech počítačích, které se potřebují k serveru přihlašovat, přidat do ~/.ssh/known_hosts veřejný klíč naší nové CA.

@cert-authority *.example.com 

SSH se již nebude ptát na potvrzení fingerprintu. Pokud soubor přidáváe na počítač, ze kterého jste certifikáty kopírovali, máte již cílový server v known_hosts přidaný. Musíme jej tedy ještě vymazat.

$ ssh-keygen -R example.com
# Host example.com found: line 3
/home/user/.ssh/known_hosts updated.
Original contents retained as /home/user/.ssh/known_hosts.old

Jak zrychlit PrestaShop 1.6 s minimem úprav

Dlouhé roky pro zákazníky dělám rychlostní optimalizace webových aplikací. Nejčastěji se přitom jedná o různý Open Source software. V případě eshopu je to o to horší, že dlouhé načítání snadno odradí zákazníky od nákupu.

PrestaShop je v dnešní době asi nejlepším Open Source eshopem vůbec. To nic nemění na tom, že pokud to myslíte s online prodejem vážně, měli byste se mu obloukem vyhnout. Nebudu se tu dnes zabývat tím, proč tomu tak je. To je téma na jiný článek. Dnes si řekneme, jak s dobou jeho načítání jednoduše něco udělat pár úpravami, pokud jej již nasazen máte.

Často zákazníkům radím, že levnější než sahat do kódu je jít na to „hrubou silou™“. Tedy pořídit výkonnější server nebo VPS. V tomto případě ale ani přesun na výkonný server dobu načítání příliš nezkrátil. Chtěl jsem tedy zkusit, co se s tím dá udělat v rámci zákazníkova malého rozpočtu.

Tento návod je pro verzi PrestaShop 1.6.1.6, ale pravděpodobně bude fungovat i na ostatní verze PrestaShop 1.6. Vliv úprav jsem měřil pomocí nástroje Blackfire.io, který byl v odhalování nejhorších prohřešků velmi nápomocen. Veškeré testy byly prováděny s vypnutou cache, abych dostal relevantní výsledky. Také jsem se primárně zaměřil jen na výpis kategorie, který trval nejdéle. Provedené úpravy však často pomůžou i v jiné části shopu.

Něco málo o množství dat v databázi eshopu:

  • 95 900 produktů
  • 3 700 kategorií
  • 129 000 obrázků
  • Jen minimum variant
  • Jen minimum vlastností produktů
  • Bez příslušenství
  • 3 základní uživatelské skupiny
  • 4 jazyky
  • Aktivovaný modul „Blok Filtrování zboží dle parametrů“ (blocklayered)

VPS server, na kterém shop běžel:

  • 8 virtuálních jader
  • 16 GB paměti RAM
  • 240 GB místa na disku (RAID10, SATA 7200rpm)

Počáteční stav načítání kategorie před úpravami vypadal takto:

Jak zrychlit PrestShop 1.6 s minimem úprav 1

Problém 1

Modul blockcategories volá v metodě getTree() pro každou jednotlivou kategorii metodu Link::getCategoryLink() (soubor classes/Link.php). Nepředává jí ovšem instanci kategorie, ale jen její id a link_rewrite. V metodě Link::getCategoryLink() pak najdeme takovýto kód:

public function getCategoryLink($category, $alias = null, $id_lang = null, $selected_filters = null, $id_shop = null, $relative_protocol = false)
{
    if (!$id_lang) {
        $id_lang = Context::getContext()->language->id;
    }

    $url = $this->getBaseLink($id_shop, null, $relative_protocol).$this->getLangLink($id_lang, null, $id_shop);

    if (!is_object($category)) {
        $category = new Category($category, $id_lang);
    }

    // Set available keywords
    $params = array();
    $params['id'] = $category->id;
    $params['rewrite'] = (!$alias) ? $category->link_rewrite : $alias;
    $params['meta_keywords'] =    Tools::str2url($category->getFieldByLang('meta_keywords'));
    $params['meta_title'] = Tools::str2url($category->getFieldByLang('meta_title'));

    // Selected filters is used by the module blocklayered
    $selected_filters = is_null($selected_filters) ? '' : $selected_filters;

    if (empty($selected_filters)) {
        $rule = 'category_rule';
    } else {
        $rule = 'layered_rule';
        $params['selected_filters'] = $selected_filters;
    }

    return $url.Dispatcher::getInstance()->createUrl($rule, $id_lang, $params, $this->allow, '', $id_shop);
}

Tedy pro každou z 3700 kategorií se vytvoří instance třídy Category a její data se načtou z databáze! Také máte pocit, že se páni vývojáři PrestaShopu někde museli upsat? Jak by tedy metoda měla vypadat?

public function getCategoryLink($category, $alias = null, $id_lang = null, $selected_filters = null, $id_shop = null, $relative_protocol = false)
{
    if (!$id_lang) {
        $id_lang = Context::getContext()->language->id;
    }

    $url = $this->getBaseLink($id_shop, null, $relative_protocol).$this->getLangLink($id_lang, null, $id_shop);

    if (!is_object($category) && !$alias) {
        $category = new Category($category, $id_lang);
    }

    // Set available keywords
    $params = array();
    $params['id'] = is_object($category) ? $category->id : $category;
    $params['rewrite'] = (!$alias) ? $category->link_rewrite : $alias;
    // $params['meta_keywords'] =    Tools::str2url($category->getFieldByLang('meta_keywords'));
    // $params['meta_title'] = Tools::str2url($category->getFieldByLang('meta_title'));

    // Selected filters is used by the module blocklayered
    $selected_filters = is_null($selected_filters) ? '' : $selected_filters;

    if (empty($selected_filters)) {
        $rule = 'category_rule';
    } else {
        $rule = 'layered_rule';
        $params['selected_filters'] = $selected_filters;
    }

    return $url.Dispatcher::getInstance()->createUrl($rule, $id_lang, $params, $this->allow, '', $id_shop);
}

Co jsme upravili?

  • Zakomentovali jsme načtení kategorie do objektu a tím snížili nápor na databázi o 3700 SQL dotazů. Samotné instanciování třídy Category nám také dost času ušetří.
  • Upravili jsme řádek 13, aby se do pole $params správně nastavilo id kategorie v závislosti na tom, zda nám do metody přijde objekt, nebo jen id.
  • Zakomentovali jsme přiřazení parametrů meta_keywords a meta_title do pole $params. Dalším zkoumáním kódu jsem zjistil, že jsou potřeba jen v případě, že si je nastavíte do pole „Cesta ke kategoriím“ v nastavení eshopu. Vůbec mě nenapadá, proč by někdo něco takového chtěl dělat, když má k dispozici parametr rewrite 🙂

Poslední bod by samozřejmě šel řešit mnohem elegantněji, podobně jako řádek 13. Museli bychom však zajistit, že nám tyto parametry přijdou už na vstupu. To by znamenalo upravit veškeré SQL dotazy, jejichž výsledky vstupují do metody Link::getCategoryLink() napříč celým shopem. To by ovšem bylo na delší dobu a nebylo to tím pádem předmětem zakázky.

Jak tato jednoduchá úprava pomohla? Zkrátili jsme načítání kategorie o 4,6 vteřiny!

Jak zrychlit PrestShop 1.6 s minimem úprav 2

Problém 2

Modul blocktopmenu volá v metodě generateCategoriesMenu() (soubor modules/blocktopmenu/blocktopmenu.php) opět naši známou Link::getCategoryLink(), jen schovanou do metody Category::getLink(). Tentokrát ale nenechává instanciování třídy Category na metodě getCategoryLink(), ale vytváří ji sám. Zkrácený kód metody vypadá takto:

protected function generateCategoriesMenu($categories, $is_children = 0)
{
    $html = '';

    foreach ($categories as $key => $category) {
        if ($category['level_depth'] > 1) {
            $cat = new Category($category['id_category']);
            $link = Tools::HtmlEntitiesUTF8($cat->getLink());
        } else {
            $link = $this->context->link->getPageLink('index');
        }

        // kód zkrácen
    }

    return $html;
}

Přitom potřebný link_rewrite ve výsledku SQL dotazu již máme. Můžeme tedy jednoduše instanciování třídy Category opět vynechat a předat metodě Link::getCategoryLink() pouze potřebné údaje:

protected function generateCategoriesMenu($categories, $is_children = 0)
{
    $html = '';

    foreach ($categories as $key => $category) {
        if ($category['level_depth'] > 1) {
            //$cat = new Category($category['id_category']);
            //$link = Tools::HtmlEntitiesUTF8($cat->getLink());
            $link = $this->context->link->getCategoryLink($category['id_category'], $category['link_rewrite']);
        } else {
            $link = $this->context->link->getPageLink('index');
        }

        // kód zkrácen
    }

    return $html;
}

Jak nám tato úprava pomohla? Zkrátili jsme načítání kategorie o dalších 700 ms! Není to již tolik jako předtím, ale dostáváme se už na vcelku přijatelné 2 vteřiny dvěma jednoduchými úpravami.

Jak zrychlit PrestShop 1.6 s minimem úprav 3

Problém 3

Modul blockcategories vybírá z databáze v metodě hookFooter() (soubor modules/blockcategories/blockcategories.php) celý strom kategorií. Přitom v šabloně, kterou zákazník použil, jsou v patičce stránky vypsány jen top-level kategorie. Je tedy trochu zbytečné vybírat kategorie všechny. Co myslíte? Můžeme tedy řádek:

$maxdepth = Configuration::get('BLOCK_CATEG_MAX_DEPTH');

snadno upravit takto:

$maxdepth = 2;

Pokud máte v patičce stránky i podkategorie, upravte hodnotu $maxdepth dle vlastního uvážení a potřebné hloubky.

Jak nám tato úprava pomohla? Snížili jsme načítání kategorie o dalších 600 ms!

Jak zrychlit PrestShop 1.6 s minimem úprav 4

Nyní už se výpis kategorie načítá vcelku přijatelných 1,39 vteřin. Moje práce pro zákazníka tedy po 2,5 hodině ladění končí. Dalo by se jistě pokračovat mnohem dál. A nejen na výpisu kategorie, ale i jiných stránkách, které mají zase svoje vlastní specifika.

Pro zajímavost ještě ukážu, jak rychle se kategorie načte po zapnutí cache:

Jak zrychlit PrestShop 1.6 s minimem úprav 5

Výkonovému ladění zdar!

Jak na vybírání pošty z jiných schránek do Seznamu

Často se setkávám s tím, že lidé, kterým píšu, mají nastaveno přesměrování z mailu na vlastní doméně do mailu na seznamu. Je to jistě nejjednodušší způsob, jak mít všechny maily v Seznam schránce, pokud jste na ni zvyklí. Mám to ale jedno úskalí. Pokud má člověk, který vám píše nastaveno zabezpečení své domény pomocí SPF, vrátí se mu chybová zpráva a mail, který vám psal, nebude doručen.

K čemu je SPF dobré a proč by vás to mělo zajímat? V dnešní době všichni bojujeme se spamem. SPF a další technologie, jako je např. DKIM přispívají k tomu, aby nám do schránek chodilo spamu méně. SPF funguje tak, že řekne cílovému serveru (v našem případě Seznamu), z jakých serverů smí přijímat emaily od daného odesílatele. Díky tomu spamer, který by chtěl poslat email vaším jménem (podvrhnout jako odesílatele vaši emailovou adresu), nepochodí, protože jeho server není povolený odesílatel pro danou emailovou adresu.

Proč tedy vadí, že si necháte přeposílat maily ze své domény na Seznam? Pokud vám pošlu mail, řekněme z adresy mujmail@mojedomena.cz na vaši adresu vasmail@vasedomena.cz a ten je přesměrován do schránky Seznamu, pochopitelně zůstane jako odesílatel mailu nastaven mujmail@mojedomena.cz. Pokud mám však svoji doménu zabezpečenou pomocí SPF, váš server není oprávněn odesílat maily z mojedmena.cz a Seznam tedy mail vyhodnotí jako spam. Vám mail nepřijde a mě přijde jen chybová zpráva.

Jak to napravit? Na Seznamu si můžete nastavit vybírání pošty z jiných schránek. Seznam pak přímo stahuje maily z vaší domény a nedochází k nežádoucímu přesměrování. Jak toto vybírání pošty nastavit?

Nejprve se přihlaste do své schránky na Seznamu. Nahoře vpravo pak klikněte na Nastavení.

Seznam_vyber_posty_1

V nově otevřeném okně klikněte v levém menu na položku Účty a import. Poté na tlačítko Přidat další účet pod nadpisem Propojení schránek.

Seznam_vyber_posty_2

V select boxech vyberete v prvním Doručené (do této složky vám budou maily z vaší domény chodit) a v druhém Jiný. Doplníte váš email, heslo ke schránce a adresu POP3 serveru. Tuto adresu by vám měl sdělit váš poskytovatel, nebo ji najdete v administraci vaší domény (tam kde si zakládáte schránky), pokud takovou od poskytovatele máte. Port by v závislosti na tom, zda mailový server na vaší doméně podporuje šifrovanou komunikaci, měl být 995. Pokud ji nepodporuje, port bude 110. Tuto informaci by vám opět měl sdělit váš poskytovatel. Je samozřejmě vždy bezpečnější používat šifrovanou komunikaci.

Seznam_vyber_posty_3

Po kliknutí na Uložit účet by mělo být vybírání schránky nastaveno.

Seznam_vyber_posty_4

Jak rozdělit GIT repozitář na více menších a zachovat/promazat historii

Semtam se stane, že se aplikace rozroste, moduly přibývají a jejich doménový model se začně čím dál více lišit. Nastal čas aplikaci rozdělit. Prvním krokem, ještě před úpravami kódu by mělo být rozdělení GIT repozitáře. Můžeme na to jít více způsoby:

1. Zkopírovat repozitář a vymazat z něj nepotřebné soubory

Je to rychlé a vcelku bezpečné. V repozitáři nám ale zůstane kompletní historie souborů ostatních aplikací a klonování každého repozitáře bude kvůli tomu trvat zbytečně dlouho.

2. Založit nový, čistý repozitář a commitnout do něj jen potřebné soubory

Je to opět rychlé a vcelku bezpečné. V repozitáři nám ale nezůstane ani historie souboru oddělované aplikace. Budeme sice mít zálohu v podobě originálního repozitáře před rozdělením, nebude se s tím ale moc pohodlně pracovat.

3. Použít git filter-branch a upravit historii zkopírovaného repozitáře

Moje oblíbená varianta. Je sice náročnější na čas a snadno se při ní udělá nějaká chyba. Odměnou je ale čistý repozitář, který obsahuje jen soubory oddělované části aplikace včetně jejich historie. Nyní si ukážeme jak na to.

Naklonujeme si originální repozitář

$ git clone git@bitbucket.org:user/repo.git new-app

Přepneme se do složky repozitáře a projistotu odstraníme jeho remote.

$ git remote remove origin

Získáme seznam všech souborů v historii včetně smazaných

Tento krok není nutný, pokud nejste puntičkáři jako já 🙂 Můžeme snadno odstranit jen soubory, které aktuálně v repozitáře nechceme. Nebo si můžeme dát trochu více práce a odstranit i soubory, které jsme už dávno smazali nebo přejmenovali. K tomu ale potřebujeme zjistit, které to byly. Pro začátek si tedy vypíšeme všechny soubory, které kdy byly do repozitáře přidány. Výstup nasměrujeme do nějakého souboru, aby se nám s ním lépe pracovalo.

$ git log --pretty=format: --name-only --diff-filter=A | sort -u > all-files.txt

Nyní přišel čas otevřít si výstupní soubor v editoru a nechat v něm jen souboru, které chceme smazat. Musíme dát pozor, abychom si nesmazali soubory, které už sice v repozitáři nejsou, ale jejich přejmenované verze chceme. V případě odstraňování celých složek můžeme ponechat jen složku místo kompletního výpisu jejich souborů.

Jakmile budeme hotovi, použijeme připravený soubor v příkazu git filter-branch k promazání historie.

$ git filter-branch --force --index-filter \
    "git rm -r --cached --ignore-unmatch `cat all-files.txt | tr '\n' ' '`" \
    --prune-empty -- --all

Nyní je načase aplikaci otestovat (v nejlepším případě spustit testy), zda jsme neodmazali něco důležitého. Nejspíše bude nutné kvůli odmazaným souborům trochu kódu upravit. Poté si můžeme přidat nový remote a udělat první push.

$ git remote add origin git@bitbucket.org:user/new-app.git
$ git push -u origin --all
$ git push origin --tags # pokud máte nějaké tagy

V této fázi jsou ještě stále staré revize zálohovány pod refs/original. Pokud je chcete z repozitáře úplně odstranit, musíme to udělat ručně.

$ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
$ git reflog expire --expire=now --all
$ git gc --prune=now

Eventuelně můžeme místo toho udělat čerstvý clone našeho již vyfiltrovaného remotu.