Перед уроком
Обери свою групу й себе зі списку. Тоді відповіді й перевірки завдань запишуться саме на тебе.
Навчитися зберігати кожну зміну бота в історії Git, за хвилину повертати зламаного бота до робочого стану, пробувати ризиковані ідеї в окремій гілці й тримати токен поза репозиторієм (так у git називають теку проєкту з історією змін)
Замість <логін> підставляєш свій
логін виду bohdan.g9: ім’я, крапка, номер групи. Кутові дужки не пишуться.
Кабінет — сторінка, де проти кожного критерію стоїть відмітка, зарахований він чи ні.
У прикладах на цій сторінці скрізь написано bohdan.g9 — читай це як свій логін.
На занятті 02 твій бот ожив: він відповідає на /start і /help і працює на сервері, навіть коли твій комп’ютер вимкнено. Сьогодні ти почнеш його змінювати — і навчишся робити це так, щоб жодна зміна не могла зламати бота назавжди. Для цього існує Git — програма, яка пам’ятає кожен збережений стан коду. Збережений стан називається коміт; що це таке, докладно розібрано в теорії.
До кінця заняття бот отримає нову команду /fact і нове привітання, а в історії з’явиться щонайменше п’ять твоїх комітів зі зрозумілими повідомленнями. Ти двічі навмисно зламаєш бота — і двічі повернеш його до робочого стану: один раз — поки зміну ще не збережено комітом, другий — коли її вже збережено. Ризикований експеримент зробиш в окремій гілці proba (так називають окрему лінію змін, яка не чіпає робочого бота), а всю історію відправиш у дві запасні копії: ~/repo.git на сервері й GitHub.
Що можна буде показати: у Telegram — відповідь на /fact і нове привітання; у терміналі — git log --oneline зі списком твоїх комітів; на GitHub — ті самі коміти й гілку proba. І ще одне, що теж перевіряється: токен бота за весь цей час жодного разу не потрапить у Git.
Ситуація, яка трапляється з кожним. Учора бот відповідав. Сьогодні ти додав нову команду, переписав привітання й поправив підказку /help — три правки в різних місцях файлу bot.py. Перезапустив службу, а бот мовчить. Яка з трьох правок його зламала? Яким був файл учора? Пам’ять на це не відповість.
Звичний спосіб захиститися — копії: bot2.py, bot_stare.py, bot_tochno_robochyi.py. За тиждень із них уже нічого не зрозуміло: яка з копій робоча, чим вони відрізняються, коли з’явилася помилка.
Git — програма, яка зберігає історію змін у теці проєкту. Вона запам’ятовує, які рядки в яких файлах змінилися, коли, хто це зробив і навіщо (це «навіщо» пишеш ти сам), і вміє повернути будь-який збережений стан. Два слова, які будуть далі в кожному розділі:
.git усередині: її назва починається з крапки, тому звичайна команда ls її не показує, а ls -a — показує;bot.py, README.md, project.yaml. Це поточний стан проєкту, а історія лежить окремо, у .git.Тека ~/app у тебе вже є репозиторієм: git там увімкнули, коли створювали акаунт, і зробили перший коміт «Заготовка проєкту». На занятті 02 ти додав туди бота й відправив роботу на GitHub.
Історія твого бота живе в трьох місцях — і це навмисно:
~/app/.git — твій робочий репозиторій. Тут ти робиш коміти;~/repo.git — запасна копія на тому самому сервері. Це bare-репозиторій (вимовляється «бер», від англійського bare — «голий»): у ньому лише історія, без робочої копії, тож файлу bot.py ти там не побачиш. Туди не заходять редагувати код, туди тільки надсилають коміти. Якщо ти випадково зіпсуєш чи видалиш ~/app, проєкт повертається з ~/repo.git однією командою git clone — вона робить новий репозиторій з усією історією й файлами;Інші репозиторії, з якими твій обмінюється комітами, називаються віддаленими (англійською remote, вимовляється «рімоут»). У кожного з них у git є коротке ім’я: ~/repo.git зветься origin, GitHub — github. Надсилає коміти команда git push:
git push origin masterЧитається так: «надішли в origin коміти гілки master». Надсилаються лише ті коміти, яких там ще немає. І важливе: push — це разова дія, а не постійний зв’язок. Що ти зберіг комітом після push, у копії не з’явиться, доки не виконаєш push знову.
Ігор виконав git push origin master, а після цього зробив ще два коміти з новою командою /fact. Наступного дня він випадково видалив теку ~/app цілком. Що можна відновити з ~/repo.git?
Коміт — збережений стан усіх файлів проєкту, за якими стежить git, на той момент, коли ти виконав команду git commit. У кожному коміті записано вміст файлів, дату й час, ім’я автора, твоє повідомлення про те, що змінилося, і хеш.
Хеш — рядок із 40 літер і цифр, який git обчислює з умісту коміту, наприклад 58d966e1c530828b4f83a026f418afb88d707fe5. Це ідентифікатор коміту: за ним коміт знаходять і на нього посилаються. Майже скрізь git друкує лише перші сім символів — 58d966e, і цього досить, щоб не сплутати один коміт з іншим.
Кожен коміт пам’ятає попередній, тому коміти вишиковуються в історію. Її видно командою git log --oneline — по рядку на коміт:
94fe014 переписав привітання /start: бот пояснює, навіщо він
3c1f8a2 переписав README: для кого бот і як його запустити
58d966e додав команду /fact: бот розповідає цікавий факт
7eb7b1f Заняття 02: бот відповідає на /start і /help
c255902 Заготовка проєктуНайновіший коміт — угорі. На нього вказує позначка HEAD — «ти зараз тут».
Між зміненим файлом і комітом є проміжна зупинка — індекс (англійською staging area, «місце підготовки»). Це список змін, які підуть у наступний коміт. Команда git add кладе зміну в індекс, команда git commit записує в історію все, що в ньому зібралося:
робоча копія ──git add──▶ індекс ──git commit──▶ історіяНавіщо ця зупинка. За вечір ти переписав привітання в bot.py і заодно доповнив README.md. Це дві різні справи, і в історії їм краще бути двома комітами: git add bot.py — коміт, потім git add README.md — ще один коміт. Через місяць ти знайдеш потрібну зміну за повідомленням, а не перебиратимеш змішане докупи.
Одна деталь, на якій часто помиляються: у коміт іде та версія файлу, яка була в момент git add. Якщо після git add ти ще раз змінив файл, нові правки лишаються поза комітом, доки не виконаєш git add знову.
Подивитися на зміни можна трьома командами. Запам’ятай, яка на яке питання відповідає:
git status — «що зараз змінено і що вже в індексі?» Угорі, під Changes to be committed, — те, що піде в коміт; нижче, під Changes not staged for commit, — змінене, але не додане; ще нижче, під Untracked files, — файли, про які git ще не знає;git diff — «що саме я змінив і ще не додав?» Друкує рядки: з мінусом — як було, з плюсом — як стало. git diff --staged показує те саме для змін, які вже лежать в індексі;git log --oneline — «що вже збережено?», тобто історія комітів. А git show показує один коміт разом із його змінами (без хеша — останній).Ось шматок виводу git diff, у якому видно додану команду /fact:
@@ -59,6 +60,8 @@ def handle_update(update):
"Напиши /help.")
if text.startswith("/help"):
return HELP
+ if text.startswith("/fact"):
+ return "Цікавий факт: на Венері доба довша за рік."
if text.startswith("/"):
return "Не знаю такої команди. Напиши /help."
return f"Ти написав: {text}"Рядок із @@ каже, де у файлі зміна: близько 60-го рядка, у функції handle_update. Цифри в цьому рядку можна не розбирати. Дивись на самі рядки: без знака — сусіди, щоб було легше зорієнтуватися, рядки з плюсом — нові. Якщо рядок змінено, diff показує його двічі: старий з мінусом, новий з плюсом.
Екран «завис» з двокрапкою внизу Довгий вивід git diff, git log чи git show git показує посторінково, як книжку: унизу екрана з’являється : або (END). Пробіл — наступна сторінка, q — вийти. Це не зависання.
Оля виконала git add bot.py. Потім помітила одруківку в тексті /help, виправила її в тому самому файлі, зберегла й одразу виконала git commit -m "додала команду /fact". Що потрапило в коміт?
Самому git байдуже, що написано в повідомленні. Воно для людини, яка читатиме історію, — найчастіше для тебе ж через місяць, коли бот почне поводитися дивно й треба буде знайти, звідки це взялося. Хороше повідомлення одним рядком відповідає на питання «що змінилося в боті?».
| Так не пишуть | Чому | Так пишуть |
|---|---|---|
fix | Не сказано, що зламалося і де | виправив /help: бот відповідав «Не знаю такої команди» |
зміни | Коміт і так складається зі змін | додав команду /fact: бот розповідає цікавий факт |
update bot.py | Назву файлу git покаже й сам | переписав привітання /start своїми словами |
123 | Через тиждень не означатиме нічого | додав у README, як перевірити бота без Telegram |
робота у вівторок | Дату й час git записує сам | проба: бот рахує слова й символи в повідомленні |
Правила прості: один рядок, до 70 символів; починай з дієслова — «додав», «виправив», «переписав», «прибрав»; пиши, що змінилося для бота, а не який файл ти відкривав. Якщо в повідомленні хочеться написати «і», найчастіше це дві різні зміни — і комітів має бути два.
Автоперевірка не рахує повідомлень, коротших за десять символів, і слів-заглушок на кшталт fix, update, wip, test. А викладач на розборі відкриває твій git log, вибирає навмання коміт і просить пояснити, що в ньому змінилося. Якщо за повідомленням ти сам не можеш цього сказати — повідомлення погане.
Повідомлення — одразу в команді Пиши його так: git commit -m "…". Без -m git відкриває текстовий редактор, щоб ти набрав повідомлення там, і на сервері це може виявитися незнайомий тобі редактор vi. Як із нього вийти — у розділі «Якщо щось не працює».
Як користуватися ШІ-помічником. ШІ добре пояснює повідомлення git: встав у чат текст помилки (лише його — без токена й без вмісту .env) і спитай: «Що це означає і що мені зробити? Я вчуся перший місяць». Але є два правила. Перше: повідомлення комітів пиши сам. ШІ не знає, що й навіщо ти змінив, а пояснювати на розборі доведеться тобі. Друге: ШІ інколи радить команди, які стирають історію або чужу роботу, — git push --force, git reset --hard, rm -rf .git. Не виконуй команду, яку не можеш пояснити. Спершу спитай: «Що саме зробить ця команда і що я можу втратити?»
І ніколи не вставляй у чат токен бота чи вміст .env: усе, що ти туди надіслав, зберігається на чужому сервері, і секретом це вже не є.
«Відкотити» означає різне — залежно від того, що сталося. Ось чотири ситуації й команда для кожної.
Перша: змінив файл, ще не комітив, і бот зламався.
git restore bot.pyФайл повертається до стану з останнього коміту. Усі правки, які ти ще не зберіг комітом, у ньому зникають — без жодного питання.
Друга: не пам’ятаєш, що саме наробив. Спершу подивись, потім вирішуй:
git diffТретя: поклав в індекс зайве — наприклад, git add прихопив не той файл. Прибрати його з індексу, не чіпаючи вмісту:
git restore --staged README.mdЧетверта: коміт уже зроблено (можливо, навіть відправлено в ~/repo.git і на GitHub), а зміна виявилася поганою.
git revert --no-edit HEADgit revert створює новий коміт, який робить протилежне до вказаного: що той додав — прибирає, що прибрав — повертає. HEAD — останній коміт; замість нього можна написати хеш будь-якого коміту з git log --oneline. Параметр --no-edit означає «візьми готове повідомлення Revert "…" і не відкривай редактора».
Чому не просто видалити поганий коміт з історії? Бо він уже може лежати в ~/repo.git і на GitHub, а видалити коміт тут — означає, що копії перестали б збігатися з тим, що в тебе: ми не можемо стерти його звідти. revert нічого не стирає: в історії видно і невдалу спробу, і її скасування. Тому він безпечний навіть після push.
Обережно з git restore Ця команда викидає незбережені зміни назавжди: git про них не знав, тож і повернути їх не зможе. Перед нею завжди дивись git diff — чи справді ти хочеш утратити те, що там написано.
Марта пів години правила bot.py і жодного разу не комітила. Тепер служба не запускається: у журналі SyntaxError. Останній коміт — учорашній, тоді бот працював. Яка команда поверне робочу версію?
Гілка — окрема лінія комітів усередині того самого репозиторію. Основна гілка у твоєму репозиторії зветься master (у багатьох інших проєктах — main; це лише ім’я). У ній лежить бот, який працює. Як зветься твоя основна гілка, покаже команда git branch --show-current.
Коли хочеш спробувати щось ризиковане — наприклад, щоб бот на кожен текст відповідав інакше, — ти створюєш другу гілку й робиш коміти в неї. Коміти другої гілки в master не потрапляють. Вийшло — переносиш їх у master. Не вийшло — повертаєшся в master, і бот такий, як до експерименту.
git switch -c proba # створити гілку proba й перейти в неї
git switch master # повернутися на основну гілку
git branch # список гілок, зірочка — поточна
git merge --no-edit proba # перенести коміти з proba в поточну гілкуПеремикання гілки змінює файли в теці. Виконав git switch master — і bot.py знову такий, як у master. Це не втрата: коміти proba лежать на місці, повернешся в неї — повернеться і її версія файлів.
Звідси важливий наслідок для бота. Служба club-bot запускає run.py, а той підключає bot.py — це файли, які зараз лежать у ~/app, тобто код поточної гілки, і Python читає їх лише під час старту. Перемкнувся на proba й перезапустив службу — у Telegram відповідає експериментальний бот. Повернувся в master — перезапусти службу ще раз, інакше в її пам’яті так і лишиться експеримент.
Злиття (англійською merge) переносить коміти однієї гілки в іншу. Якщо в master після створення proba нових комітів не було, git просто пересуває master уперед, до останнього коміту proba, і пише Fast-forward — «перемотування вперед». Параметр --no-edit і тут рятує від редактора, якщо git захоче записати окремий коміт злиття.
Олег створив гілку proba, зробив у ній коміт «проба: бот відповідає великими літерами», потім виконав git switch master і python3 try_bot.py "привіт". Бот відповів звичайними літерами. Що сталося?
Не все з теки ~/app має потрапляти в історію. Три групи файлів туди не кладуть:
.env з токеном бота BOT_TOKEN. Токен — це пароль бота: хто його знає, той керує ботом;.db. Бази в тебе ще немає, вона з’явиться на наступному занятті й житиме в ~/data. Історія коду й дані людей — різні речі, і зберігають їх окремо;__pycache__ (Python кладе туди підготовлену до запуску версію твоїх файлів, щоб наступний запуск був швидшим) і теку venv з бібліотеками. Їх не зберігають: програми відтворюють їх самі або однією командою. На нашому сервері бібліотеки лежать у спільному /opt/club/venv, тож у твоїй теці venv немає, але правило на нього вже є..gitignore — файл зі списком того, що git навмисно не помічає. Кожен рядок у ньому — правило: ім’я файлу (.env), ім’я теки з косою рискою в кінці (__pycache__/) або шаблон із зірочкою (*.db означає «будь-яке ім’я, що закінчується на .db»). Рядки, що починаються з #, — коментарі для людини. Твій .gitignore прийшов із заготовкою, і всі ці правила в ньому вже є. Саме тому на занятті 02 git status не показав .env, а git add -A його не прихопив. Якщо спробувати додати .env навмисно, git відмовиться й порадить ключ -f («примусово»). Для секретів цей ключ заборонений.
Тепер найважливіше. Файл, який хоч раз потрапив у коміт, називається відстежуваним. Правила .gitignore діють лише на невідстежувані файли. Якщо .env уже потрапив у коміт, а правило дописали пізніше, git і далі стежитиме за файлом, а стара версія з токеном назавжди лишиться в тому коміті.
Чому такий токен вважають украденим, навіть якщо файл потім прибрали:
git show;~/repo.git, GitHub і кожен, хто колись зробив собі копію репозиторію через git clone. Ти не знаєш, хто їх уже бачив;Хто має токен, той може від імені твого бота читати повідомлення, які йому пишуть однокласники, і відповідати їм що завгодно. Тому порядок дій один, і він не залежить від того, «чи хтось устиг побачити»:
@BotFather, надішли /revoke і вибери свого бота. BotFather видасть новий токен, а старий одразу перестане діяти.~/app/.env замість старого.systemctl --user restart club-bot — тепер бот працює з новим токеном..env з відстежуваних: git rm --cached .env і коміт. Файл лишиться на диску, а git перестане за ним стежити.Набагато простіше не допускати цього взагалі: .gitignore пишуть до того, як з’являється секрет, а перед кожним комітом дивляться git status.
Андрій випадково додав .env у коміт з токеном бота й відправив коміт на GitHub. Наступного дня помітив, дописав .env у .gitignore і зробив новий коміт. Що тепер із токеном?
| Слово | Що означає |
|---|---|
| Git | Програма, яка зберігає історію змін у теці проєкту й уміє повернути будь-який збережений стан |
| репозиторій | Тека проєкту разом з усією історією; сама історія — у прихованій теці .git |
| робоча копія | Файли, які ти бачиш і редагуєш зараз |
| коміт | Збережений стан файлів з автором, датою й повідомленням |
| хеш | Ідентифікатор коміту з 40 символів; зазвичай показують перші сім |
| HEAD | Позначка «ти зараз тут» — останній коміт поточної гілки |
| індекс | Список змін, підготовлених до наступного коміту; туди кладе git add |
| git status | Що змінено і що вже лежить в індексі |
| git diff | Які рядки змінено: з мінусом — як було, з плюсом — як стало |
| git log | Історія комітів; з --oneline — по рядку на коміт |
| git show | Один коміт разом з його змінами |
| git restore | Повертає файл до стану з останнього коміту; з --staged — прибирає зміну з індексу |
| git revert | Новий коміт, який скасовує вказаний; історія при цьому не стирається |
| гілка | Окрема лінія комітів; основна в тебе — master |
| git switch | Перейти на іншу гілку; з -c — створити гілку й перейти в неї |
| злиття, git merge | Перенесення комітів з іншої гілки в поточну |
| Fast-forward | Злиття, коли гілку просто пересувають уперед, без окремого коміту |
| віддалений репозиторій | Інший репозиторій, з яким обмінюються комітами: origin (~/repo.git) і github |
| bare-репозиторій | Репозиторій без робочої копії, лише історія — як ~/repo.git |
| git push | Надіслати свої коміти у віддалений репозиторій |
| .gitignore | Список того, що git навмисно не помічає |
| відстежуваний файл | Файл, який уже був у коміті; правила .gitignore на нього не діють |
| git rm --cached | Перестати відстежувати файл, лишивши його на диску |
| токен | Пароль бота. Потрапив у коміт — вважай украденим |
| /revoke | Команда @BotFather: видає новий токен, а старий перестає діяти |
Скрізь замість bohdan.g9 підставляй свій логін. Усі команди git виконуй у теці ~/app. Якщо в тебе немає Telegram, роби все те саме, а бота перевіряй командою python3 try_bot.py: вона викликає ту саму функцію handle_update, що й живий бот.
ssh bohdan.g9@91.219.61.4
cd ~/app
pwd
systemctl --user is-active club-bot
python3 try_bot.py "/start"pwd має надрукувати /home/bohdan.g9/app — git працюватиме саме з цим репозиторієм. systemctl --user is-active club-bot відповідає active: служба з ботом працює. try_bot.py друкує привітання бота, наприклад Привіт, Тест! Я бот проєкту «…». Напиши /help. Ім’я «Тест» підставляє try_bot.py.
Якщо служба failed або try_bot.py друкує помилку, бот із заняття 02 зламаний, і спершу його треба полагодити: tail -n 30 ~/logs/club-bot.log покаже причину. Починати історію змін зі зламаного бота не варто.
Git записує в кожен коміт ім’я й пошту автора. Перевір, чи вони вже задані:
git config --global user.name
git config --global user.emailЯкщо обидві команди щось надрукували — пропусти наступний блок. Якщо у відповідь порожньо, назви себе. Ім’я пиши без прізвища: історію бачить викладач, а на GitHub — кожен, кому ти покажеш репозиторій.
git config --global user.name "Богдан"
git config --global user.email "$(whoami)@club.local"$(whoami) термінал замінить твоїм логіном, як на занятті 02. Пошту git нікуди не надсилає, це лише підпис; адреса club.local вигадана навмисно, а справжню пошту сюди не пиши — вона потрапила б у кожен коміт. --global означає «для всіх репозиторіїв твого акаунта», тому робиться це один раз. І ще одне налаштування — щоб git, коли йому знадобиться редактор, відкривав знайомий тобі nano:
git config --global core.editor nano
git config --global --listПерша команда нічого не друкує — так і має бути. Друга показує все разом: рядки user.name=…, user.email=… і core.editor=nano.
ls -a
git status
git log --oneline
git branch --show-current
git remote -vls -a покаже й приховані файли, чиї назви починаються з крапки: .git (це історія), .gitignore, .env, .env.example. Серед звичайних буде ще тека __pycache__ — її створив Python, коли запускав try_bot.py;git status має сказати On branch master і nothing to commit, working tree clean — усе із заняття 02 збережено комітами. Може бути ще рядок Your branch is up to date with 'github/master': GitHub знає ті самі коміти;git log --oneline покаже кілька рядків: унизу «Заготовка проєкту», вище — коміти заняття 02. Якщо ти вже працював з git раніше, рядків буде більше — нічого страшного, нові коміти ляжуть зверху;git branch --show-current надрукує назву основної гілки — master. Якщо в тебе main, далі скрізь пиши main замість master;git remote -v — чотири рядки: github з адресою git@github.com:…/club-app.git і origin зі шляхом /home/bohdan.g9/repo.git, кожен двічі — (fetch) і (push). Це ті два віддалені репозиторії з теорії. Якщо на занятті 02 ти не заводив GitHub (немає 13 років або дозволу батьків), рядків буде два, лише origin, — і це нормально: працюй з ~/repo.git, а кроки з github пропускай.Якщо git status показує змінені файли (modified), щось із заняття 02 лишилося без коміту. Подивись git diff і, якщо все гаразд, збережи комітом: git add -A, потім git commit -m "Заняття 02: бот відповідає на /start і /help".
cat .gitignore
git check-ignore -v .env __pycache__ app.dbУ .gitignore є рядки .env, *.db, venv/ і __pycache__/. Друга команда для кожного імені друкує правило, яке його приховує:
.gitignore:12:.env .env
.gitignore:4:__pycache__/ __pycache__
.gitignore:8:*.db app.dbЧитається так: файл із правилами, номер рядка в ньому, саме правило, а після проміжку — ім’я, яке воно приховує. Номери рядків у тебе можуть бути інші. Файлу app.db ще немає, але правило *.db уже на нього чекає.
Якщо для якогось імені рядка немає або в cat .gitignore бракує .env, *.db чи venv/ (так буває в тих, хто починав курс раніше й писав .gitignore сам), — зупинись і допиши відсутні правила в кінець файлу, кожне окремим рядком, наприклад echo "venv/" >> .gitignore (два знаки >> дописують рядок у кінець). Потім знову запусти git check-ignore -v і збережи комітом: git add .gitignore, git commit -m "доповнив .gitignore: .env, база й venv не потрапляють у Git". Автоперевірка L03.2 дивиться саме на ці три правила.
Тепер спробуй навмисно додати токен у Git. Це безпечно: git не дасть.
git add .envThe following paths are ignored by one of your .gitignore files:
.env
hint: Use -f if you really want to add them.Git відмовився й нічого не додав (нижче може бути ще рядок-підказка, як вимкнути це повідомлення). Параметр -f з підказки для .env не використовуй ніколи. Ще дві перевірки:
git ls-files
git log --all --oneline -- .envgit ls-files друкує повний список файлів, за якими стежить git: bot.py, env.py, run.py, try_bot.py, .env.example, README.md та інші. Серед них не має бути .env і файлів .db. (.env.example — інший файл, токена в ньому немає, і в репозиторії йому саме місце: з нього видно, які налаштування потрібні боту.)
Друга команда шукає в усіх гілках коміти, у яких хоч раз був файл .env, і має не надрукувати нічого. Порожньо — токен ніколи не потрапляв у Git. Якщо з’явився хоч один рядок, переходь до розділу «Якщо щось не працює», останній пункт.
nano bot.pyЗнайди підказку HELP і допиши один рядок про нову команду — між рядком про /about і рядком про відлуння. Решту рядків не чіпай: кожен із них уже закінчується на \n (так у тексті підказки позначають перехід на новий рядок), тому змінюється лише один рядок:
HELP = ("Що я вмію:\n"
"/start — привітання\n"
"/help — ця підказка\n"
"/about — хто мене зробив\n"
"/fact — цікавий факт\n"
"будь-який інший текст — повторю його")Тепер у функції handle_update знайди перевірку /about (її ти додав на занятті 02) і одразу під її return встав два нові рядки — перед рядком if text.startswith("/"):. Факт вигадай свій, про тему твого проєкту; нижче приклад для бота про космос:
if text.startswith("/about"):
return f"Проєкт «{PRODUCT}», автор — {CFG['product']['author']}."
if text.startswith("/fact"):
return "Цікавий факт: на Венері доба довша за рік."
if text.startswith("/"):
return "Не знаю такої команди. Напиши /help."Що тут відбувається. Кожна команда — це перевірка «якщо повідомлення починається з такого-то слова, поверни такий-то текст». Відступи — рівно 4 і 8 пробілів, як у сусідніх рядках, а текст відповіді — у лапках, які треба закрити.
Чому саме тут, а не в кінці функції? Python перевіряє умови згори вниз і виконує першу, яка підійшла. Рядок if text.startswith("/") ловить будь-яку команду, якої бот не знає. Якщо поставити /fact нижче за нього, до /fact черга не дійде ніколи.
Збережи (Ctrl+O, Enter) і вийди (Ctrl+X). Перевір бота й подивись, що змінилося:
python3 try_bot.py "/fact"
python3 try_bot.py "/help"
git diff/fact відповідає твоїм фактом. /help показує шість рядків, серед них новий. git diff показує три рядки з плюсом і жодного з мінусом: один у HELP і два в handle_update. Старих рядків ти не міняв, тож їх diff не позначає.
Якщо замість відповіді бачиш SyntaxError, Python пише номер рядка й ставить знак ^ під місцем помилки: найчастіше там загублена лапка, дужка або двокрапка. Якщо два рядки підказки злилися в один (/about — хто мене зробив/fact — …), ти забув \n. Відповідь Не знаю такої команди на /fact означає, що команду вставлено нижче за перевірку /.
Усе працює — записуємо в історію й оновлюємо живого бота:
git add bot.py
git commit -m "додав команду /fact: бот розповідає цікавий факт про тему проєкту"
systemctl --user restart club-bot
systemctl --user is-active club-botgit commit відповідає приблизно так: [master 58d966e] додав команду /fact: бот розповідає цікавий факт про тему проєкту і нижче 1 file changed, 3 insertions(+). Це гілка, короткий хеш (у тебе свій), твоє повідомлення і скільки рядків додано й прибрано. Служба — active. Напиши боту в Telegram /fact — він відповість так само, як try_bot.py.
README.md — головний опис проєкту. Його першим бачить кожен, хто відкриває твій репозиторій на GitHub. Зараз у ньому текст заготовки.
nano README.mdЗроби три речі:
# Мій сервіс заміни назвою свого бота;## Що це прибери службовий текст між знаками підкреслення — він починається словами «Заняття 01: опиши одним абзацом» — і напиши свій абзац: для кого бот, що він уже вміє і як його знайти в Telegram;## Як запустити допиши, що бот живе в службі club-bot, де лежить токен і як перевірити бота без Telegram. Команди, які там уже є, лиши.Нижче зразок для чужого бота: він показує, з чого складаються розділи. У тебе інший бот і інший текст, тому пиши своє.
# Бот-помічник для походу
## Що це
Telegram-бот @bohdan_pohid_bot для однокласників, які вперше йдуть
у похід з ночівлею. Зараз він вітається (/start), показує свої
команди (/help), розповідає про проєкт (/about), ділиться
цікавим фактом (/fact) і повторює текст, який йому написали.
## Як запустити
Бот живе у службі club-bot на сервері (її запускає run.py), тому відповідає,
навіть коли мій комп'ютер вимкнено. Токен бота лежить у файлі .env,
якого в репозиторії немає навмисно: його роблять з .env.example
(cp .env.example .env && chmod 600 .env) і вписують токен від @BotFather.
systemctl --user restart club-bot # перезапуск після зміни коду
tail -n 30 ~/logs/club-bot.log # останні рядки журналу
Перевірити бота без Telegram:
python3 try_bot.py "/start"Заголовки лишай точно такими: два знаки решітки, пробіл, текст. Автоперевірка шукає саме ## Що це і ## Як запустити, а службового тексту заготовки там бути не має. Токена, паролів і свого прізвища в README не пиши: цей файл бачитимуть на GitHub. Збережи файл і збережи комітом:
git diff
git add README.md
git commit -m "переписав README: для кого бот і як його запустити"git diff покаже рядки заготовки з мінусом і твій текст із плюсом. Коміт відповість рядком [master …] з твоїм повідомленням.
Вправа на індекс: ти зробиш дві різні зміни, а збережеш їх двома окремими комітами.
Перша — у bot.py. Перепиши привітання /start своїми словами: скажи, навіщо цей бот. {name} і {PRODUCT} лиши — це ім’я людини й назва проєкту. Наприклад:
if text.startswith("/start"):
return (f"Привіт, {name}! Я бот проєкту «{PRODUCT}». "
"Я допомагаю зібратися в похід. Напиши /help.")Друга — у README.md. Нижче, у розділі ## Структура, є таблиця файлів. Допиши рядки про файли бота одразу під її останнім рядком (з deploy.sh), без порожнього рядка між ними — інакше таблиця розірветься. Якщо такого розділу в тебе немає, допиши в кінець файлу рядок ## Структура, а під ним — ці рядки:
| `bot.py` | логіка бота: що він відповідає на кожне повідомлення |
| `env.py` | читає токен і налаштування з `.env` |
| `try_bot.py` | перевірка бота без Telegram |Збережи обидва файли й подивись:
python3 try_bot.py "/start"
git statusБот вітається по-новому. git status показує обидва файли в списку Changes not staged for commit. Покладемо в індекс лише один:
git add bot.py
git status
git diff --stagedТепер bot.py стоїть угорі, під Changes to be committed, а README.md лишився нижче. git diff --staged показує тільки зміну привітання: саме вона піде в коміт. Записуємо одну зміну, потім другу:
git commit -m "переписав привітання /start: бот пояснює, навіщо він"
git add README.md
git commit -m "додав у README опис файлів бота"
git log --onelineУ git log --oneline зверху два нові рядки, кожен про своє. Перезапусти службу (systemctl --user restart club-bot) і напиши боту /start у Telegram.
Ламаємо навмисно — так, як ламається по-справжньому: одним пропущеним символом.
nano bot.pyЗнайди рядок if text.startswith("/help"): і зітри двокрапку в кінці. Збережи й вийди. Перевір:
python3 try_bot.py "/help"Замість відповіді — повідомлення про помилку. Його кінець такий:
File "/home/bohdan.g9/app/bot.py", line 61
if text.startswith("/help")
^
SyntaxError: expected ':'Python не зміг навіть прочитати файл: expected ':' — «очікував двокрапку» в рядку 61 (у тебе номер може бути інший). Тепер подивись, що стане з живим ботом:
systemctl --user restart club-bot
tail -n 15 ~/logs/club-bot.logУ журналі той самий SyntaxError (якщо його ще немає, зачекай кілька секунд і повтори другу команду). Служба падає ще під час запуску, а systemd за правилом Restart=always кожні три секунди пробує запустити її знову — і знову марно. Напиши боту в Telegram /help: він мовчить. Ось чим небезпечна одна загублена двокрапка. Повертаємо:
git status
git diff
git restore bot.py
git diffgit status показує modified: bot.py. Перший git diff — рядок із двокрапкою з мінусом і без неї з плюсом: оце й уся поломка. Після git restore другий git diff не друкує нічого: файл знову такий, як в останньому коміті. Перезапускаємо:
python3 try_bot.py "/help"
systemctl --user restart club-bot
systemctl --user is-active club-botПідказка знову на місці, служба active. А повідомлення, які ти писав зламаному ботові, за кілька секунд отримають відповідь: Telegram тримав їх, доки бот не повернувся. Запам’ятай порядок: журнал → git diff → git restore → перезапуск.
Буває гірше: зміну вже зберегли комітом, а потім з’ясувалося, що вона ламає бота. Відтворимо це. Уяви, що ти вирішив «навести порядок» і перенести перевірку невідомих команд на самий верх.
nano bot.pyУ handle_update постав курсор на рядок if text.startswith("/"): і двічі поспіль натисни Ctrl+K — виріжуться він і рядок під ним (return "Не знаю такої команди…"). Стрілками підніми курсор на рядок if text.startswith("/start"):, на самий його початок (клавіша Home), і натисни Ctrl+U — вирізане вставиться над ним. Якщо курсор стоятиме посеред рядка, вставка розріже рядок навпіл. Збережи, вийди й збережи комітом, ніби все гаразд:
git diff
git add bot.py
git commit -m "перемістив перевірку невідомих команд вище"
python3 try_bot.py "/help"
python3 try_bot.py "/about"git diff показав, що два рядки зникли внизу й з’явилися вгорі. А бот і на /help, і на /about відповідає Не знаю такої команди. Напиши /help. Усі команди починаються з /, тож перша ж перевірка тепер ловить їх усі. Коміт поганий — скасовуємо:
git log --oneline -3
git show HEAD
git revert --no-edit HEADgit show HEAD показує останній коміт: автора, дату, повідомлення й ті самі переставлені рядки. git revert відповідає:
[master 654d9b6] Revert "перемістив перевірку невідомих команд вище"
Date: …
1 file changed, 2 insertions(+), 2 deletions(-)У рядку Date: — дата й час нового коміту. Перевір, що бот знову розуміє команди, і подивись історію:
python3 try_bot.py "/help"
git log --oneline -3
systemctl --user restart club-botПідказка знову працює. У git log видно обидва коміти: поганий і його скасування. Нічого не стерто, і через місяць буде зрозуміло, що тут сталося. Службу між цими кроками ти не перезапускав, тож живий бот увесь час працював зі старим, правильним кодом; перезапуск наприкінці — щоб служба точно взяла поточну версію файлу.
git switch -c proba
git branchGit відповідає Switched to a new branch 'proba', а git branch показує дві гілки, зірочка — проти proba. Тепер експеримент: хай бот на звичайний текст ще й рахує слова й символи. Відкрий nano bot.py і заміни останній рядок handle_update — return f"Ти написав: {text}" — на два:
words = len(text.split())
return f"Ти написав: {text}\nСлів: {words}, символів: {len(text)}."text.split() розрізає текст на слова за пробілами, а len() рахує, скільки вийшло шматків (або скільки символів у рядку). Перевір і збережи комітом у гілку:
python3 try_bot.py "привіт, як справи"
git add bot.py
git commit -m "проба: бот рахує слова й символи в повідомленні"Бот відповідає двома рядками: Ти написав: привіт, як справи і Слів: 3, символів: 17. Відповідь git на коміт починається з [proba …]: коміт записано в гілку proba. Хочеш побачити експеримент у Telegram — перезапусти службу: вона візьме файли поточної гілки. Тепер повертайся:
git switch master
python3 try_bot.py "привіт, як справи"
git log --oneline --graph --all -4У master бот відповідає по-старому, одним рядком. Остання команда малює історію всіх гілок:
* 51b16fb (proba) проба: бот рахує слова й символи в повідомленні
* 654d9b6 (HEAD -> master) Revert "перемістив перевірку невідомих команд вище"
* 389010d перемістив перевірку невідомих команд вище
* 7abcaeb додав у README опис файлів ботаПозначки в дужках показують, де закінчується кожна гілка: proba на один коміт попереду master, а HEAD -> master означає, що ти зараз на master. Хеші в тебе свої, і можуть бути ще позначки origin/master чи github/master — так git показує, до якого коміту дійшла кожна копія. Тепер вирішуй сам:
master: git merge --no-edit proba. Git відповість Fast-forward: master просто пересунувся до коміту proba;master лишається без нього, а експеримент чекає в proba.В обох випадках гілку proba не видаляй: автоперевірка шукає в репозиторії другу гілку. І перезапусти службу, щоб живий бот працював з кодом master:
systemctl --user restart club-botgit branch --show-current
git push origin master
git push origin probaПерша команда має надрукувати master: відправляємо з основної гілки. git push друкує кілька рядків підрахунку (Enumerating objects…, Writing objects…) — це git пакує коміти. Важливий останній рядок:
To /home/bohdan.g9/repo.git
c255902..654d9b6 master -> masterЗліва — коміт, який був у ~/repo.git до команди, справа — який став після неї. Для proba буде * [new branch] proba -> proba: такої гілки там ще не було. Перевір, що коміти дійшли:
git log --oneline -1
git -C ~/repo.git log --oneline -1 master
git -C ~/repo.git branchПараметр -C означає «виконай команду в іншій теці, не переходячи туди». Перші два рядки мають бути однакові — той самий хеш і те саме повідомлення: це й перевіряє критерій L03.4. Остання команда показує дві гілки, master і proba.
git status
git log --oneline
git ls-files
git log --all --oneline -- .env
git branchgit status — nothing to commit, working tree clean;git log --oneline — над «Заготовкою проєкту» щонайменше п’ять твоїх комітів, і кожен ти можеш пояснити своїми словами;git ls-files — без .env і без файлів .db;git log --all --oneline -- .env — порожньо;git branch — master і proba, зірочка проти master. Якщо зірочка проти proba, виконай git switch master: автоперевірка дивиться на поточну гілку.Третя копія — поза сервером. Віддалених репозиторіїв у тебе два, тому в git push назву пишемо явно: origin — це ~/repo.git, github — GitHub. Якщо GitHub у тебе немає (заняття 02), виконай лише коміт і git push origin master, а рядки з github пропусти: автоперевірка дивиться на ~/repo.git.
cd ~/app
git switch master
git add -A
git commit -m "Заняття 03: команда /fact, відкат поломок і гілка proba"
git push origin master
git push github master
git push github probaЯкщо git switch каже Already on 'master' — ти вже на основній гілці. Якщо git commit відповідає nothing to commit, working tree clean, усе вже лежить у комітах — це добре, переходь до push. Перший push оновлює ~/repo.git (потрібен, якщо щойно з’явився новий коміт), два наступні — GitHub. Останній рядок кожного — стрілка master -> master або proba -> proba; для proba на GitHub — * [new branch]. Відповідь Everything up-to-date означає, що відправляти нічого: ця копія вже має всі твої коміти.
Наприкінці наступних занять роби так само: коміт, потім git push origin master і git push github master — тоді обидві копії знають усю твою роботу.
Відкрий свій репозиторій club-app на github.com і онови сторінку. Має бути:
git log;master) — ще й proba;bot.py, .env.example, а під списком — твій README.md з розділами «Що це» і «Як запустити». Файлу .env там немає — і не має бути.Якщо push відповів Permission denied (publickey), GitHub не впізнав ключ сервера. Виконай ssh -T git@github.com і дивись розділ «Якщо щось не працює».
Сьогодні автоперевірка не розмовляє з ботом — вона читає твій репозиторій ~/app. Рахує коміти з осмисленими повідомленнями (заготовка не входить, як і повідомлення, коротші за десять символів, чи слова-заглушки на кшталт fix і update), дивиться список відстежуваних файлів і правила .gitignore, рахує гілки, шукає твій останній коміт у ~/repo.git, читає розділи README.md і переглядає історію всіх гілок — чи не був там хоч раз файл .env. Якість повідомлень (L03.6) оцінює викладач: відкриє твій git log, вибере коміт і попросить пояснити, що в ньому змінилося.
Перевірка дивиться на гілку, на якій ти стоїш, і порівнює її останній коміт з ~/repo.git. Тому перед нею виконай git switch master і git push origin master. Запустити перевірку можна будь-коли кнопкою «Перевірити практичну частину» у розділі «Перевірка виконаних завдань» нижче на сторінці; крім того, вона сама проходить за розкладом.
| Код | Що перевіряється | Якщо не зараховано |
|---|---|---|
| L03.1 | ≥5 комітів з осмисленими повідомленнями автоперевірка | Змістовних комітів менше за п'ять. Заготовка не рахується, як і повідомлення коротші за десять символів чи «fix», «update». Роби наступні коміти змістовними. |
| L03.2 | venv, база й .env не відстежуються git автоперевірка | У git потрапило те, чого там бути не повинно: venv, файл бази (.db) або .env. Додай ці шляхи в .gitignore, а вже відстежувані прибери з індексу: git rm --cached .env |
| L03.3 | існує друга гілка автоперевірка | У репозиторії лише одна гілка. Створи другу: git switch -c proba |
| L03.4 | ~/repo.git містить ті самі коміти автоперевірка | Останнього коміту немає в ~/repo.git — зміни не відправлено. Виконай git push origin master (назву твоєї гілки покаже git branch --show-current) |
| L03.5 | README має розділи «Що це» і «Як запустити» автоперевірка | У ~/app/README.md мають бути розділи «## Що це» і «## Як запустити» з твоїм текстом (не менше 60 символів у кожному), а службовий рядок «Заняття 01: опиши одним абзацом…» треба видалити. |
| L03.6 | якість повідомлень комітів викладач | Позначку ставить викладач на занятті або після нього. |
| L03.11 | токен ніколи не потрапляв у Git автоперевірка | Файл .env колись потрапив у коміт, і токен лишився в історії назавжди. Прибери .env з індексу (git rm --cached .env), а токен вважай украденим: @BotFather, /revoke, новий токен у .env. Якщо коміт уже відправлено в ~/repo.git чи на GitHub, історію чистить викладач — самому цей пункт не виправити. |
Кожне питання оцінюється відразу в браузері — це лишається так само. Ця кнопка лише надсилає підсумок учителю, щоб відповіді не зникли разом із закритою вкладкою.
Список критеріїв і що саме вони означають — у таблиці вище, «Що перевіряє автоматика». Тут — та сама перевірка, але прямо зараз, без очікування на найближчий прохід за розкладом.
/time. Додай боту команду, яка каже, котра година, — так само, як /fact: перевірка в handle_update вище за if text.startswith("/") і рядок у HELP. datetime у bot.py уже імпортовано (на початку файлу є рядок from datetime import datetime), тож відповідь може бути такою: return f"Зараз {datetime.now():%H:%M}." Перевір через try_bot.py і порівняй зі своїм годинником. Не збігається? Виконай на сервері date і подивись, який часовий пояс він друкує: годинник сервера налаштований не на київський час. Виправ відповідь так, щоб час був київський:
if text.startswith("/time"):
from zoneinfo import ZoneInfo
kyiv = datetime.now(ZoneInfo("Europe/Kyiv"))
return f"У Києві зараз {kyiv:%H:%M}."README.md комусь із рідних або однокласнику й нічого не пояснюй уголос. Запиши, де людина зупинилася й що перепитала, і виправ саме ці місця. Кожне виправлення — окремим комітом зі змістовним повідомленням.git log -p -3 — три останні коміти разом зі змінами — і перечитай кожен рядок із плюсом: на наступному занятті викладач покаже будь-який із них і попросить пояснити. Спробуй також git log -S "/fact" --oneline: ця команда знаходить коміт, у якому з’явилося слово /fact.proba-pryvit і навчи в ній бота окремо відповідати на слово «привіт» (перевірка if text.lower() == "привіт": перед останнім рядком функції). Перевір, вирішуй, чи переносити в master, і відправ обидві гілки в обидві копії.git status — working tree clean; git push origin master і git push github master; git log --oneline -1 і git -C ~/repo.git log --oneline -1 master друкують той самий коміт.Якщо внизу екрана двокрапка : або (END), це не редактор, а посторінковий перегляд довгого виводу git log, git diff чи git show. Натисни q.
Якщо екран заповнили рядки зі знаком ~ ліворуч, а літери друкуються дивно, — це редактор vi. Git відкриває його, коли ти забув -m у git commit або --no-edit у git revert чи git merge, а редактор у налаштуваннях не вказано. Вихід:
git commit без -m — натисни Esc, набери :q! і Enter. Коміт скасується (Aborting commit due to empty commit message), повтори його з -m "…";git revert і git merge — Esc, потім :wq і Enter: git візьме запропоноване повідомлення й закінчить роботу.Щоб це не повторювалося, один раз виконай git config --global core.editor nano. Відтепер git відкриватиме nano: повідомлення пишеш у першому рядку, потім Ctrl+O, Enter, Ctrl+X.
Git не знає, яким іменем підписати коміт, і поруч друкує Author identity unknown. Коміт при цьому не створено. Назви себе (крок 2) і повтори команду:
git config --global user.name "Богдан"
git config --global user.email "$(whoami)@club.local"
git config --global --listВідповідь error: Your local changes to the following files would be overwritten by checkout означає: у тебе є зміни, не збережені комітом у файлі, який у двох гілках різний, і перемикання їх знищило б. Git відмовився й нічого не зачепив. Подивись, що це за зміни:
git status
git diffЗміни потрібні — збережи комітом їх у поточну гілку (git add, git commit -m "…"). Не потрібні — git restore назва_файлу. Після цього git switch спрацює.
Пройди по черзі:
git branch --show-current
git status
python3 try_bot.py "/start"
systemctl --user restart club-bot
systemctl --user status club-bot
tail -n 30 ~/logs/club-bot.logproba — служба запускає код експериментальної гілки. Виконай git switch master і перезапусти службу;git status показує modified — у файлі правки без коміту, можливо, зламані: git diff покаже які, git restore bot.py поверне останній коміт;try_bot.py друкує помилку — у журналі служби буде та сама, з номером рядка. Поки її не виправлено, служба падатиме під час кожного запуску;try_bot.py відповідає правильно, а в Telegram бот по-старому — службу не перезапустили після зміни: вона читає код лише під час старту.Якщо systemctl --user status club-bot показує inactive поза заняттям — служби груп увімкнені за розкладом (заняття й вечір з 17:00 до 23:00). Telegram тримає повідомлення до доби: бот відповість, щойно служба підніметься.
error: src refspec main does not match any (або master) — гілки з такою назвою в тебе немає. git branch --show-current покаже справжню назву, підстав її: git push origin master;Permission denied (publickey) при відправці на GitHub — GitHub не впізнав ключ сервера. Перевір ssh -T git@github.com: має бути рядок зі словом successfully і твоїм іменем на GitHub. Якщо ні — відкритий ключ (cat ~/.ssh/id_ed25519.pub) треба знову додати на GitHub у Settings → SSH and GPG keys, як на занятті 02;fatal: 'github' does not appear to be a git repository — віддаленого репозиторію з іменем github немає: на занятті 02 ти його не підключив або назвав інакше. git remote -v покаже, які є. Без акаунта на GitHub цей push просто пропускай; акаунт є — підключи його, як на занятті 02: git remote add github git@github.com:<логін-github>/club-app.git;! [rejected] … (fetch first) і Updates were rejected — на GitHub є коміти, яких немає в тебе. Найчастіше так буває, якщо ти правив файл просто на сайті GitHub. Забери їх і відправ своє:git pull --rebase github master
git push github masterПараметр --rebase тут обов’язковий: без нього git зупиниться з повідомленням Need to specify how to reconcile divergent branches і нічого не зробить. З ним git спершу забирає коміти з GitHub, а потім кладе твої поверх них.
Так буває, коли ти скасовуєш не останній коміт, а давніший, і ті самі рядки відтоді змінювалися ще раз: git не може сам вирішити, яку версію лишити. Поверни все як було до команди:
git revert --abort
git statusgit status має знову сказати working tree clean. Скасовуй коміти по черзі, починаючи з найновішого, — або поклич викладача й розберіться разом.
Ознаки: git ls-files показує .env, git log --all --oneline -- .env друкує рядки або критерій L03.11 червоний. Спершу — токен, бо він уже вважається вкраденим:
@BotFather — /revoke, вибери бота, отримай новий токен;~/app/.env замість старого й перезапусти службу: systemctl --user restart club-bot.Потім — репозиторій. Перестань відстежувати файл і перевір, що правило на місці:
git rm --cached .env
git check-ignore -v .envЯкщо .env потрапив лише в один коміт — git log --all --oneline -- .env друкує один рядок, і це твій останній коміт, — і цей коміт ще нікуди не відправлено (його немає ні в git -C ~/repo.git log --oneline -1 master, ні на GitHub), перепиши його без .env: git commit --amend --no-edit. Після цього git log --all --oneline -- .env не друкує нічого, і L03.11 стане зеленим. Якщо коміт уже відправлено, зроби звичайний коміт git commit -m "прибрав .env з репозиторію": git перестане стежити за файлом, але в копіях старий коміт лишиться — історію в трьох місцях чистять разом з викладачем. Захищає тебе в будь-якому разі новий токен, а не зелена позначка.
Якщо git rm --cached .env відповідає did not match any files, зараз git за .env не стежить — файл був лише в старому коміті.