Страницы

Поиск по вопросам

среда, 10 октября 2018 г.

Как реально удалить в git формально удаленные данные?

Допустим, мы хотим взять какой-то проект и откатиться к его началу, напрочь удалив все лишнее.
Казалось бы, что надо удалить все лишние ветки, обрезать оставленную и может быть почистить мусор. Но не тут-то было, git не хочет просто так ничего забывать, а просто делает вид, что информации уже не существует.
Делаем клон:
cd /tmp git clone https://github.com/avp210159/hello avp@wubu:tmp$ cd hello
Поскольку я пользуюсь алиасами из tutorial, то выведу их тут:
avp@wubu:hello$ git config -l user.name=avp user.email=avp210159@gmail.com alias.co=checkout alias.ci=commit alias.st=status alias.br=branch alias.hist=log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short alias.histt=log --pretty=format:"%h T=%t %ad | %s%d [%an]" --graph --date=short alias.type=cat-file -t alias.dump=cat-file -p core.editor=vi avp@wubu:hello$
Запоминаем всю историю "проекта" с ее SHA
avp@wubu:hello$ git hist --all * 80333c5 2016-06-20 | merge with pull origin (origin/mtime) [avp] |\ | * e86897b 2016-06-17 | mtime in branchmtime on avp-ubu1 [avp] | * f028a44 2016-06-16 | Update README (local version) [avp] | * 9a8ad84 2016-06-14 | fix README merge conflict [avp] | |\ | | * 6502c2b 2016-06-09 | new .mtimes filename and hook script + [avp] | * | 54f9e11 2016-06-14 | change pre-commit description in README [avp] | * | 2f6e07f 2016-06-09 | new .mtimes filename and hook script ++ [avp] | |/ | * 39f7393 2016-06-09 | new .mtimes filename and hook script [avp] | * 7f9be15 2016-06-07 | add BRANCH= to Notes [avp] | * 2c79116 2016-06-07 | restore mtimes in master [avp] | * e572dc2 2016-06-07 | ya some changes in Notes and mtimes script [avp] | * 9430c0f 2016-06-07 | some changes in Notes and mtimes script [avp] | * 33336a8 2016-06-07 | switch to style ... [avp] | * c9d1277 2016-06-07 | delete .gittimes/ from .gitignore [avp] | * 95bc9f3 2016-06-07 | mtimes pre-commit test2 [avp] | * 4d74da9 2016-06-07 | mtimes pre-commit 1-st [avp] | * a929ae6 2016-06-07 | delete .gitimes/, switch to pre-commit .mtimes [avp] | * eeceb8b 2016-06-07 | mtimes in pre-commit (initial test 2 (--amend)) [avp] | * e0debec 2016-06-06 | mtimes sentinel again 3-nd [avp] | * bb90a66 2016-06-06 | mtimes sentinel again 2-nd [avp] | * b8658c0 2016-06-06 | mtimes sentinel again [avp] | * 4a1df91 2016-06-06 | mtimes sentinel 3-st [avp] | * fd787f2 2016-06-06 | mtimes with sentinel 2-nd () [avp] | * f0463c9 2016-06-06 | mtimes with sentinel 1-st [avp] | * 66be459 2016-06-03 | mtime postcommit ./gitimes +n-th mistake [avp] | * 20c41f6 2016-06-03 | mtime postcommit ./gitimes +n-th [avp] | * e9606f8 2016-06-03 | mtime postcommit ./gitimes n-th [avp] | * 204f16f 2016-06-03 | mtime postcommit ./gitimes [avp] | * 44956af 2016-06-03 | mtime postcommit 5-th [avp] | * 114cf75 2016-06-03 | mtime postcommit 4-th [avp] | * 81ddd75 2016-06-03 | mtime with nested commit in postcommit 2-nd [avp] | * 1cc0c8a 2016-06-03 | mtime with nested commit in postcommit 2-nd [avp] | * a59abd9 2016-06-03 | mtime with nested commit in postcommit [avp] | * 312e92b 2016-06-03 | mtime postcommit 3 4 [avp] | * a54cd63 2016-06-03 | mtime postcommit 3 [avp] | * a1aee23 2016-06-03 | mtime postcommit 2 [avp] * | 3c4de65 2016-06-20 | ya commit [avp] * | 307bd84 2016-06-20 | delete .gitimes/... again (was restored at play with history) [avp] * | ce922e3 2016-06-20 | rebase ... try delete a lot of histiry ... [avp] * | 2f7aecd 2016-06-14 | change pre-commit description in README [avp] * | 0dd8e2a 2016-06-09 | new .mtimes filename and hook script ++ [avp] * | 88c5a7d 2016-06-03 | mtime with nested commit in postcommit 2-nd [avp] * | 6ddf211 2016-06-03 | mtime with nested commit in postcommit 2-nd [avp] * | a78f31e 2016-06-03 | mtime postcommit 2 [avp] |/ * a31734c 2016-06-02 | mtime work begins [avp] | * f80dd69 2016-06-20 | Merge branch 'master' of https://github.com/avp210159/hello (HEAD -> master, origin/master, origin/HEAD) [avp] | |\ | | * eb0216f 2016-06-17 | mtime on avp-ubu1 [avp] | * | 3e24a50 2016-06-20 | update a bit Git-Notes master version [avp] | |/ | * a06157e 2016-06-16 | Change README (local master version refer to mtime README) [avp] | * 1e15bc5 2016-06-16 | make mtime branch, restore README & Notes from it [avp] |/ * d4aaa79 2016-05-26 | try .md formatting in README [avp] * b9db73c 2016-05-25 | github push from home OK [Vasily] * 1ff513c 2016-05-25 | change Git-Notes, try PUSH! [avp] * b6a9482 2016-05-25 | change readme, try to pull changes after clone [avp] * f7ae83c 2016-05-25 | hide email [Vasily] * eeac95f 2016-05-25 | add .gitignore ~/.gitconfig as my-home-... [avp] * 566eb66 2016-05-25 | add can do/can't do in Git-Notes [avp] * 213ddd5 2016-05-25 | add README.md for git [avp] * 4e2c742 2016-05-25 | result of curl in Git-Notes [avp] * 57953e4 2016-05-25 | add Git-Notes file [avp] | * 2d6f376 2016-06-17 | mtimes on style avp-ubu1 (origin/style) [avp] | * e312891 2016-06-16 | Update README.md [Vasily] | * d8fb896 2016-06-16 | test .md ref to other file in github in README [avp] | * ae6d0e1 2016-06-14 | Create README in this branch [Vasily] | * a13af39 2016-06-14 | in style mtimes on too [avp] | * 56494cb 2016-06-07 | some changes in .mtimes pre-commit [avp] | * 1a0adf6 2016-06-07 | add .gitignore from master [avp] | * e466e8c 2016-05-24 | Updated index.html [avp] | * c17f477 2016-05-24 | Hello uses style.css [avp] | * 0bfa599 2016-05-24 | Added css stylesheet [avp] |/ * 8a6023f 2016-05-24 | Added index.html. [avp] * a828998 2016-05-24 | Moved hello.html to lib [avp] * ba52194 2016-05-24 | Add author/email [avp] * f9de6a7 2016-05-20 | Added HTML header (tag: v1) [avp] * 8d54403 2016-05-20 | Added standard HTML page tags (tag: v1-beta) [avp] * 05bb38e 2016-05-20 | Edit

tag [avp] * a7da921 2016-05-20 | First Commit [avp] avp@wubu:hello$
Смотрим состояние
avp@wubu:hello$ git st On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean avp@wubu:hello$ git br * master
и отсоединяемся от удаленного репозитория
avp@wubu:hello$ git remote rm origin
Снова смотрим историю (теперь уже видна только master ветка)
avp@wubu:hello$ git hist --all * f80dd69 2016-06-20 | Merge branch 'master' of https://github.com/avp210159/hello (HEAD -> master) [avp] |\ | * eb0216f 2016-06-17 | mtime on avp-ubu1 [avp] * | 3e24a50 2016-06-20 | update a bit Git-Notes master version [avp] |/ * a06157e 2016-06-16 | Change README (local master version refer to mtime README) [avp] * 1e15bc5 2016-06-16 | make mtime branch, restore README & Notes from it [avp] * d4aaa79 2016-05-26 | try .md formatting in README [avp] * b9db73c 2016-05-25 | github push from home OK [Vasily] * 1ff513c 2016-05-25 | change Git-Notes, try PUSH! [avp] * b6a9482 2016-05-25 | change readme, try to pull changes after clone [avp] * f7ae83c 2016-05-25 | hide email [Vasily] * eeac95f 2016-05-25 | add .gitignore ~/.gitconfig as my-home-... [avp] * 566eb66 2016-05-25 | add can do/can't do in Git-Notes [avp] * 213ddd5 2016-05-25 | add README.md for git [avp] * 4e2c742 2016-05-25 | result of curl in Git-Notes [avp] * 57953e4 2016-05-25 | add Git-Notes file [avp] * 8a6023f 2016-05-24 | Added index.html. [avp] * a828998 2016-05-24 | Moved hello.html to lib [avp] * ba52194 2016-05-24 | Add author/email [avp] * f9de6a7 2016-05-20 | Added HTML header (tag: v1) [avp] * 8d54403 2016-05-20 | Added standard HTML page tags (tag: v1-beta) [avp] * 05bb38e 2016-05-20 | Edit

tag [avp] * a7da921 2016-05-20 | First Commit [avp] avp@wubu:hello$
Уходим куда-то к началу наших забав:
avp@wubu:hello$ git reset --hard 8a6023f HEAD is now at 8a6023f Added index.html. avp@wubu:hello$ avp@wubu:hello$ git br * master avp@wubu:hello$ git st On branch master nothing to commit, working directory clean avp@wubu:hello$ ll total 32 drwxrwxr-x 4 avp avp 4096 Jun 23 15:53 ./ drwxrwxrwt 15 root root 16384 Jun 23 15:50 ../ drwxrwxr-x 8 avp avp 4096 Jun 23 15:54 .git/ -rw-rw-r-- 1 avp avp 95 Jun 23 15:50 index.html drwxrwxr-x 2 avp avp 4096 Jun 23 15:50 lib/
На всякий случай "почистим" и видим:
avp@wubu:hello$ git gc Counting objects: 69, done. Delta compression using up to 2 threads. Compressing objects: 100% (34/34), done. Writing objects: 100% (69/69), done. Total 69 (delta 25), reused 65 (delta 24) avp@wubu:hello$ git hist --all * 8a6023f 2016-05-24 | Added index.html. (HEAD -> master) [avp] * a828998 2016-05-24 | Moved hello.html to lib [avp] * ba52194 2016-05-24 | Add author/email [avp] * f9de6a7 2016-05-20 | Added HTML header (tag: v1) [avp] * 8d54403 2016-05-20 | Added standard HTML page tags (tag: v1-beta) [avp] * 05bb38e 2016-05-20 | Edit

tag [avp] * a7da921 2016-05-20 | First Commit [avp] avp@wubu:hello$
что все вроде бы ОК.
Но, что если посмотреть на запомненные SHA и ...
avp@wubu:hello$ git co e86897b Note: checking out 'e86897b'.
You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example:
git checkout -b
HEAD is now at e86897b... mtime in branchmtime on avp-ubu1 avp@wubu:hello$ ll total 64 drwxrwxr-x 4 avp avp 4096 Jun 23 15:57 ./ drwxrwxrwt 15 root root 16384 Jun 23 15:50 ../ -rwxrwxr-x 1 avp avp 440 Jun 23 15:57 .cur-mtimes-avp-ubu1-avp* -rwxrwxr-x 1 avp avp 379 Jun 23 15:57 .cur-mtimes-wubu-avp* drwxrwxr-x 8 avp avp 4096 Jun 23 15:57 .git/ -rw-rw-r-- 1 avp avp 17 Jun 23 15:57 .gitignore -rw-rw-r-- 1 avp avp 10980 Jun 23 15:57 Git-Notes -rw-rw-r-- 1 avp avp 95 Jun 23 15:50 index.html drwxrwxr-x 2 avp avp 4096 Jun 23 15:57 lib/ -rw-rw-r-- 1 avp avp 327 Jun 23 15:57 my-home-.gitconfig -rw-rw-r-- 1 avp avp 1054 Jun 23 15:57 README.md avp@wubu:hello$ git br * (HEAD detached at e86897b) master avp@wubu:hello$ git st HEAD detached at e86897b nothing to commit, working directory clean avp@wubu:hello$ tail README.md
echo \#$(date) WD:$(pwd) \($BRANCH $(git rev-parse HEAD)\) > $MTIMES cat $TNEW >> $MTIMES chmod +x $MTIMES rm -f $TNEW git add $MTIMES

it's mtime branch README avp@wubu:hello$ cat .cur-mtimes-wubu-avp #Thu Jun 16 16:11:00 MSK 2016 WD:/home/avp/src/git_tutorial/work/hello (## mtime 9a8ad84f9a085333d05051600dee9d412ad9c7f5) touch -m -t 201606071534.57 '.gitignore' touch -m -t 201606071523.21 'Git-Notes' touch -m -t 201606161610.04 'README.md' touch -m -t 201606021438.31 'index.html' touch -m -t 201606031443.13 'lib/hello.html' touch -m -t 201606021438.31 'my-home-.gitconfig' avp@wubu:hello$
классно... (а ведь хотели-то все лишнее удалить). Кстати, обратите внимание, что мы видим удаленную ветку mtime, а вовсе не удаленную уже часть оставленной ветки master.
Затем возвращаемся назад:
avp@wubu:hello$ git co master Warning: you are leaving 46 commits behind, not connected to any of your branches:
e86897b mtime in branchmtime on avp-ubu1 f028a44 Update README (local version) 9a8ad84 fix README merge conflict 54f9e11 change pre-commit description in README ... and 42 more.
If you want to keep them by creating a new branch, this may be a good time to do so with:
git branch e86897b
Switched to branch 'master' avp@wubu:hello$ ll total 32 drwxrwxr-x 4 avp avp 4096 Jun 23 15:59 ./ drwxrwxrwt 15 root root 16384 Jun 23 15:58 ../ drwxrwxr-x 8 avp avp 4096 Jun 23 15:59 .git/ -rw-rw-r-- 1 avp avp 95 Jun 23 15:50 index.html drwxrwxr-x 2 avp avp 4096 Jun 23 15:59 lib/ avp@wubu:hello$
и опять наслаждаемся самообманом.
Каким образом, не воссоздавая по шагам в новом каталоге новую историю, можно откатить клон, реально удалив все не нужное? (да, я знаю о существовании git-filter-branch, которая все же позволяет Rewrite branches, но как-то это у них сложновато выглядит...).


Ответ

Говоря упрощенно, git gc удаляет устаревшие недостижимые объекты. По-умолчанию, недостижимый объект устаревает через 2 недели. Объект не достижим, если:
Он не входит коммиты некоторой ветки (после сброса ветки master объекты "вверх" по истории стали недостижимыми) На него нет ссылок из reflog. В данном случае, reflog содержит ссылку на f80dd69, появившуюся после клонирования, - она и препятствует очистке.
Поэтому сначала удалим из reflog записи с недостижимыми (в смысле истории веток) объектами:
git reflog expire --expire-unreachable=now --all
Затем удалим мусор, считая недостижимые объекты уже устаревшими, не дожидаясь двух недель:
git gc --prune=now

Обратный перебор вектора с size_t

Как мы знаем для перебора вектора / массива лучше использовать переменную типа size_t. Но когда нужно перебирать в обратном порядке возникает проблема.
for (size_t i = size - 1; i >= 0; i--) cout << a[i];
Когда i дойдет до нуля, произойдет переполнение. Мой знакомый предложил простой способ решения проблемы.
for (size_t i = size; i > 0; i--) cout << a[i - 1];
Но как-то некрасиво выходит, все-таки i - 1 а обращаемся к элементу i. Так вот вопрос, что лучше, использовать size_t как показано выше или использовать int?
for (int i = (int)size - 1; i >= 0; i--) cout << a[i];


Ответ

Для перебора вектора лучше использовать итератор. В случае работы задом наперед, подойдет реверсный итератор:
for(auto i=a.rbegin();i!=a.rend();++i) { }
Но если хочется использовать индекс - нет проблем:
for(size_t i=a.size();i--;) { }
Работает эта штука так - на каждой итерации она сначала проверяет, что i не ноль, после этого делает декремент и идет в тело цикла. В цикле получаются правильные индексы. После последней итерации, будет 0--, и i станет равна 2^( sizeof(size_t)*8 ) - 1, но нам это будет безразлично.
Замечу, что в случае такой работы с массивом перестанут работать некоторые оптимизации, например, векторизация или раскрутка цикла.

Получение описания ПК программно

В свойствах "Компьютера", в блоке "Имя компьютера, имя домена и параметры рабочей группы". Имя компьютера и параметры раб.группы можно получить программно, используя методы классы Environments. А как программно получить описание компьютера(на скриншоте - между "Полное имя" и "Рабочая группа")?


Ответ

В классе System.Environments нет методов, возвращающих описание компьютера. Однако есть другие возможности получить описание компьютера. Рассмотрим более детально два варианта решения поставленной задачи.
Решение #1
Можно получить описание компьютера из реестра. Есть путь реестра, где лежит описание компьютера:
HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\srvcomment
Используя метод Registry.GetValue необходимо сделать следующее:
using Microsoft.Win32;
string key = @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters"; string computerDescription = (string)Registry.GetValue(key, "srvcomment", null);
Решение #2
Можно использовать ManagementObjectSearcher. Пример кода:
public static void RetrieveComputerProps() { //initialize the select query with command text SelectQuery query = new SelectQuery(@"Select * from Win32_ComputerSystem");
//initialize the searcher with the query it is supposed to execute using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query)) { //execute the query foreach (ManagementObject process in searcher.Get()) { //print system info process.Get(); Console.WriteLine("/*********Computer System Information ***************/"); Console.WriteLine("{0}{1}", "Caption :", process["Caption"]); Console.WriteLine("{0}{1}", "Description:", process["Description"]); Console.WriteLine("{0}{1}", "Manufacturer:", process["Manufacturer"]); Console.WriteLine("{0}{1}", "Model:", process["Model"]); Console.WriteLine("{0}{1}","TotalPhysicalMemory:",process["TotalPhysicalMemory"]); } } }
Полученный результат:

Как добавить пустую папку в репозитории Git?

Как добавить пустую папку (без файлов) в репозиторий Git? Вариант, закомитить папку с файлами, а потом их удалить - не подходит.


Ответ

Полностью пустую папку создать нельзя, git не позволит.
Стандартная практика такая: в нужной папке создается пустой (0 байт) файл с именем .gitkeep. Можно добавить его, тогда git заметит и папку.
Распространённая неудачная практика: создавать файл с именем .gitignore. Это имя зарезервировано для другой задачи, не стоит использовать его не по назначению.

Flexbox item — перенос на новую строку

Есть Flexbox сетка.
.flex { display: flex; flex-wrap: wrap; border: 2px solid red; } .item { width: 50px; height: 50px; margin: 5px; border: 2px solid blue; }


Как перенести .new-string на новую строку вместе с элементами, которые идут после него?


Ответ

Перевод ответа на enSO
Примечание переводчика: flex-элемент (flex item) — непосредственный ребёнок блока с display: flex

Наиболее простое и надёжное решение — это вставка flex-элементов в правильных местах. Если они достаточно широкие (width: 100%), они будут создавать перенос строки.
.container { background: tomato; display: flex; flex-flow: row wrap; align-content: space-between; justify-content: space-between; } .item { width: 100px; background: gold; height: 100px; border: 1px solid black; font-size: 30px; line-height: 100px; text-align: center; margin: 10px } .item:nth-child(4n - 1) { background: silver; } .line-break { width: 100%; }

1
2
3
4
5
6
7
8
9
10

Но это уродливо и не семантически. Вместо этого, мы можем генерировать псевдоэлементы внутры flex-контейнера и использовать свойство order чтобы перемещать их в нужные места.
.container { background: tomato; display: flex; flex-flow: row wrap; align-content: space-between; justify-content: space-between; } .item { width: 100px; background: gold; height: 100px; border: 1px solid black; font-size: 30px; line-height: 100px; text-align: center; margin: 10px } .item:nth-child(3n) { background: silver; } .container::before, .container::after { content: ''; width: 100%; order: 1; } .item:nth-child(n + 4) { order: 1; } .item:nth-child(n + 7) { order: 2; }
1
2
3
4
5
6
7
8
9

Но есть ограничение: flex-контейнер может иметь только псевдоэлементы ::before и ::after. Это значит, что мы можем создать только 2 переноса строки.
Чтобы разрешить это, вы можете генерировать псевдоэлементы внутри flex-элементов вместо flex-контейнера. Таким способом вы не будете ограничены двумя. Но эти псевдоэлементы не будут flex-элементами, поэтому не будут создавать переносы строк.
Но, к счастью, спецификация CSS Display L3 ввела display: contents (в данный момент поддерживаемая только Firefox 37):
Элемент сам по себе не генерирует никаких блоков, но его дети и псевдоэлементы генерируют блоки как обычно. С целью генерации блоков и разметки элемент будет расцениваться как будто он был заменён его детьми и псевдоэлементами в дереве документа.
Поэтому вы можете применять display: contents к детям flex-контейнера и обернуть содержимое каждого внутрь дополнительного блок. Таким образом flex-элементы будут этими дополнительными обёртками и псевдоэлементами детей.
.container { background: tomato; display: flex; flex-flow: row wrap; align-content: space-between; justify-content: space-between; } .item { display: contents; } .item > div { width: 100px; background: gold; height: 100px; border: 1px solid black; font-size: 30px; line-height: 100px; text-align: center; margin: 10px; } .item:nth-child(3n) > div { background: silver; } .item:nth-child(3n)::after { content: ''; width: 100%; }
1
2
3
4
5
6
7
8
9
10

Кроме того, в соответствии с Fragmenting Flex Layout и CSS Fragmentation, flexbox позволяет принудительные переносы с помощью break-before, break-after или их псевдонимов в CSS 2.1:
.item:nth-child(3n) { page-break-after: always; /* Синтаксис CSS 2.1 */ break-after: always; /* Новый синтаксис */ }
.container { background: tomato; display: flex; flex-flow: row wrap; align-content: space-between; justify-content: space-between; } .item { width: 100px; background: gold; height: 100px; border: 1px solid black; font-size: 30px; line-height: 100px; text-align: center; margin: 10px } .item:nth-child(3n) { page-break-after: always; background: silver; }
1
2
3
4
5
6
7
8
9
10

Принудительные переносы строк во flexbox ещё широко не поддерживаются, но они работают в Firefox.

Как проверить корректность имени создаваемого файла?

Есть строка, в которой пользователь указал имя создаваемого файла. Есть необходимость проверить корректность имени. Если есть недопустимые символы, то заменить их, к примеру на "_". try не подходит, т.к. причин исключений при создании файла много (нет места на жестком диске, нет прав на создание в данной директории и т.д.). Подскажите, как проверить корректность имени?


Ответ

Для себя я сделал вот такой метод расширения:
///

/// удаляет все символы которые не разрешены в именах файлов /// /// /// public static String RemoveInvalidChars(this String file_name) { foreach (Char invalid_char in Path.GetInvalidFileNameChars()) { file_name = file_name.Replace(oldValue: invalid_char.ToString(), newValue: ""); } return file_name; }
в параметр newValue можете добавить символ по своему усмотрению
Если хотите одной строкой то можно вот так:
String new_file_name = Path.GetInvalidFileNameChars().Aggregate(invalid_file_name, (current, invalid_char) => current.Replace(invalid_char.ToString(), "_"));

Движение поезда с помощью SVG

У меня есть картинка, на которой изображено движение поезда по рельсам. Для создания анимации движения хочу использовать SVG При всём этом у паровоза из дымовой трубы выходит дым. Как сделать подобную анимацию, используя SVG?


Ответ

Поэтапно сделаем каждую деталь поезда. В основном будут использованы такие основные команды: path, rect, circle, line
Изначально создадим паровоз:
.st0 { fill: #A82F2F; stroke: #9F9F9F; } .st1 { opacity: 0.5; fill: #5B5B5B; stroke: #842323; } .st2 { fill: #D34040; stroke: #9F9F9F; stroke-width: 0.5; } .st3 { fill: #932727; } .st4 { fill: #D34040; } .st5 { fill: #5B4343; } .first-tr-str0 { fill: #DDCCCC; stroke: #EAE5E5; }
Создаём оставшиеся прицепы поезда:

Сделаем рельсы от начала до конца ViewBox

Теперь мы должны сделать анимацию дыма, выходящего из трубы паровоза:
Будем использовать filter & animateTransform
.st0 { fill: #A82F2F; stroke: #9F9F9F; } .st1 { opacity: 0.5; fill: #5B5B5B; stroke: #842323; } .st2 { fill: #D34040; stroke: #9F9F9F; stroke-width: 0.5; } .st3 { fill: #932727; } .st4 { fill: #D34040; } .st5 { fill: #5B4343; } .first-tr-str0 { fill: #DDCCCC; stroke: #EAE5E5; }
Почти готово, осталось соединить всё в одну конструкцию и сделать анимацию движения поезда по заданной траектории, используя path
* { margin: 0; padding: 0; } svg { background-color: #ddd; } .st0 { fill: #A82F2F; stroke: #9F9F9F; } .st1 { opacity: 0.5; fill: #5B5B5B; stroke: #842323; } .st2 { fill: #D34040; stroke: #9F9F9F; stroke-width: 0.5; } .st3 { fill: #932727; } .st4 { fill: #D34040; } .st5 { fill: #5B4343; } .first-tr-str0 { fill: #DDCCCC; stroke: #EAE5E5; }
Update: Использовал для оптимизации SVG
Связанный вопрос: Как нарисовать автомобиль с помощью команд SVG