День 11 · Своя функция: объявление, параметры, возврат · страница 7 из 7

Первый репозиторий: git

За сегодня вы дважды меняли программу и дважды могли её испортить. В починках помог ~/.course/orig — там лежит оригинал, положенный курсом. В настоящей работе такого каталога нет: нужен инструмент, который запоминает состояние файлов по вашей команде и умеет показать, что с прошлого раза изменилось. Это git. Он отвечает на три вопроса: что я наменял с утра и не сломал ли лишнего; как этот файл выглядел вчера, до того как я всё переписал; в каком виде работа была, когда она последний раз работала.

Сегодня — только локальный репозиторий: папка с историей лежит на этой же машине. Отправка куда-то наружу будет позже в курсе.


Подпись автора — не регистрация

Первое, что просит git, — назваться. Каждая запись в истории подписана именем и адресом почты, иначе непонятно, кто её сделал.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git config --global user.name "Ivan Petrov"
stagiaire@lab:~/gocourse/day11$ git config --global user.email "ivan@example.com"
stagiaire@lab:~/gocourse/day11$ git config --global init.defaultBranch main
stagiaire@lab:~/gocourse/day11$ git config --list --global
user.name=Ivan Petrov
user.email=ivan@example.com
init.defaultbranch=main

Это не регистрация. Ничего никуда не отправляется, учётной записи не создаётся, пароля нет. Команда дописывает три строки в файл .gitconfig в вашем домашнем каталоге — можете открыть его и убедиться. Адрес почты никто не проверяет: он нужен, чтобы в истории проекта две записи от разных людей отличались друг от друга.

Третья строка — про имя первой ветки. Что такое ветки, разберём позже в курсе; пока достаточно знать, что у истории есть основная линия: исторически git называл её master, сейчас принято main. Без этой настройки git init печатает тринадцать строк совета со словом hint: в начале каждой — с настройкой он молчит.

Если не назваться, первый же коммит закончится вот так:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git commit -m "проба"
Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got 'stagiaire@lab.(none)')

В последней строке в скобках — то, что git попытался угадать сам: ваше имя пользователя и имя машины.


Репозиторий: git init

Ситуация. Папка дня ~/gocourse/day11 — это то, что нужно сохранять: в ней задания, черновик, программы. Делаем из неё репозиторий.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git init
Initialized empty Git repository in /home/stagiaire/gocourse/day11/.git/

Появилась скрытая папка .git — в ней git хранит историю. Сами файлы никуда не делись и не изменились: репозиторий — это та же папка, просто теперь за ней наблюдают.

Дальше — главная команда дня:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	cost/
	fix1/
	fix2/
	go.mod
	report/
	scratch/
	trace/

nothing added to commit but untracked files present (use "git add" to track)

Читается сверху вниз: ветка main; коммитов пока нет; файлы, о которых git знает, что они есть, но не следит за ними (Untracked). Папки показаны одной строкой: раз внутри не отслеживается ничего, перечислять каждый файл незачем. Последняя строка — что делать дальше.

git status выполняют чаще всех остальных команд вместе взятых: она всегда говорит, в каком состоянии дела и что напрашивается следующим.


add и commit: два шага, а не один

Ситуация. Сохранить состояние — это две отдельные команды, и это не лишняя формальность.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git add cost/main.go
stagiaire@lab:~/gocourse/day11$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   cost/main.go

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	cost/TASK.txt
	cost/example.txt
	fix1/
	fix2/
	go.mod
	report/
	scratch/
	trace/

git add — «отложи это для сохранения». Отложенное git называет индексом; в status это раздел Changes to be committed, и в нём теперь одна строка. Остальное по-прежнему Untracked — и обратите внимание: папка cost больше не показана целиком, потому что один файл в ней уже отслеживается.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git commit -m "cost: стоимость заказа функцией"
[main (root-commit) c11a2c7] cost: стоимость заказа функцией
 1 file changed, 38 insertions(+)
 create mode 100644 cost/main.go

git commit записывает отложенное в историю. Одна такая запись — коммит. В первой строке ответа: ветка main, пометка root-commit (первый коммит в истории), короткий номер коммита и ваше сообщение; дальше сводка — сколько файлов и строк записано и что появилось впервые. Номер и числа у вас будут свои: они считаются из содержимого.

-m — сообщение. Без него git откроет текстовый редактор и будет ждать текста там, поэтому всегда пишите -m сразу. В сообщении — одна строка о том, что сделано, а не какие файлы тронуты: «вынес расчёт коробок в функцию», а не «поправил main.go». Через неделю по этой строке вы будете искать нужное место в истории.

Остальное откладывается одной командой: git add . — точка здесь обычная, из терминала, «текущий каталог и всё, что ниже». Второй коммит забирает всю папку дня, и в его ответе будет по строке create mode на каждый новый файл. После него:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git status
On branch main
nothing to commit, working tree clean

working tree clean — «всё, что на диске, уже записано в историю». Это то состояние, в котором приятно заканчивать день.


Что изменилось: git diff

Ситуация. Поработали полчаса, что-то правили в нескольких местах. Перед тем как сохранять, надо посмотреть, что именно поменялось. Здесь в cost/main.go изменена одна строка — обычная цена стала 5000 копеек:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git diff
diff --git a/cost/main.go b/cost/main.go
index 2cb172a..09646cb 100644
--- a/cost/main.go
+++ b/cost/main.go
@@ -12,7 +12,7 @@ func cost(qty int) int {
 	if qty >= 20 {
 		return qty * 3900
 	}
-	return qty * 4500
+	return qty * 5000
 }
 
 func main() {

Это тот же diff, что вы смотрели в починках дня 08, только сравнивает он не два файла, а «как было в последнем коммите» и «как сейчас на диске». Строка со знаком - — было, со знаком + — стало; строки без знака показаны, чтобы понять место, а @@ -12,7 +12,7 @@ — с какой строки файла и сколько строк показано.

Важная тонкость: git diff показывает только то, что ещё не отложено в индекс. Сразу после git add . он напечатает пустоту — не потому, что изменений нет, а потому, что они уже отложены; отложенное показывает git diff --staged.

Короткая сводка без подробностей:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git status -s
 M cost/main.go

M — modified, файл изменён. ?? — git про такой файл ничего не знает.

Ритм работы получается такой, и он одинаковый у всех:

  1. что-то сделали и проверили запуском;
  2. git status — что вообще изменилось;
  3. git diff — что именно изменилось в этих файлах;
  4. git add . — отложить;
  5. git commit -m "что сделано" — записать.

Сегодня git появился на последней странице, поэтому первый коммит записывает сразу всю работу дня: до него коммитить было нечего. Этот ритм описывает обычную работу, когда репозиторий уже есть: закончили кусок, проверили запуском, записали — а не один коммит в конце. Тогда в истории видно, в каком порядке всё происходило. Сегодня так получится только с третьим коммитом, и он ниже, в блоке «Попробуйте сейчас».


История: git log

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git log --oneline
07deaa0 новая цена 5000 коп.
dbfd1ad задания дня 11
c11a2c7 cost: стоимость заказа функцией

--oneline — по строке на коммит: короткий номер и сообщение. Сверху самый свежий. Без этого ключа git log печатает по пять строк на коммит с автором и датой — и открывает их постранично: выход оттуда по q, как из less в дне 01.


Что не хранить в истории: .gitignore

Ситуация. Собрали программу — рядом с исходником появился файл, который получается из него автоматически.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11/cost$ go build -o prog .
stagiaire@lab:~/gocourse/day11/cost$ ls
TASK.txt  example.txt  main.go  prog
stagiaire@lab:~/gocourse/day11/cost$ cd ..
stagiaire@lab:~/gocourse/day11$ git status -s
?? cost/prog

Собранная программа в историю не нужна: она большая, меняется при каждой сборке и в любой момент получается заново из исходника. Чтобы git перестал о ней спрашивать, в папке репозитория создают файл .gitignore — список того, что не нужно хранить, по строке на правило:

✎ Наберите в файл .gitignore
prog

Одна строка prog значит «файл с таким именем — в любой папке репозитория». Поэтому собирать удобно всегда одинаково: go build -o prog . — и одного правила хватит на все задания.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day11$ git status -s
?? .gitignore
stagiaire@lab:~/gocourse/day11$ git check-ignore -v cost/prog
.gitignore:1:prog	cost/prog

cost/prog из status пропал, зато появился сам .gitignore — его в историю как раз кладут: правила нужны всем, кто работает с этим проектом. git check-ignore -v отвечает, какое правило и из какой строки какого файла спрятало путь; молчит — значит, путь не игнорируется.

Осторожно с именами папок. Строка cost в .gitignore спрятала бы не только собранную программу, но и всю папку cost вместе с исходником. Правило должно называть то, что получается из сборки, — имя собранного файла.


Два пункта смотрят не файлы, а ваши команды

У сегодняшнего задания четыре пункта, и они разной природы. git и gitignore смотрят результат: есть ли коммиты, спрятан ли собранный файл. А t_gitdiff и t_log смотрят журнал команд — список того, что вы набирали в терминале. Результата у git diff и git log нет: они ничего не меняют, только печатают, — и узнать, смотрели вы историю или нет, можно единственным способом.

Журнал ведёт маленький хук оболочки: он дописывает каждую вашу команду в файл ~/.course/cmdlog. Ставится он один раз, командой course setup-shell (день 0), и начинает работать после того, как вы заново откроете терминал. Пункты с именами на t_ есть с дня 02 — t_gorun, t_vet, t_file, t_pipe, t_diff, — и все они про журнал.

Поэтому бывает так: работа сделана целиком, а пункт красный. Это не про вашу работу — это про то, что команду не видно. Проверяет это course doctor: в его ответе первая строка — про журнал, «журнал команд включён» или «журнал команд НЕ включён», и рядом — сколько команд в нём уже есть. Эта команда стоит первым шагом в блоке ниже.

Если журнал не включён, выполните course setup-shell, закройте терминал, откройте заново и повторите команды пункта: те, что были набраны до включения, в журнал не попали. Посмотреть, что в нём записано, можно как в обычном файле: tail ~/.course/cmdlog.


Попробуйте сейчас: три коммита.

Цель: в ~/gocourse/day11 есть репозиторий, в нём не меньше трёх коммитов, а собранные программы в историю не попадают.

1. Проверьте журнал команд — без него пункты t_gitdiff и t_log не засчитаются, сколько бы вы ни работали:

▶ Выполните
course doctor

Первая строка ответа должна быть «журнал команд включён». Если нет — course setup-shell, потом закройте и откройте терминал заново.

2. Назовитесь и создайте репозиторий:

▶ Выполните · выделенное замените своим
git config --global user.name "ВАШЕ ИМЯ"
git config --global user.email "ВАША ПОЧТА"
git config --global init.defaultBranch main
cd ~/gocourse/day11
git init
git status

Вместо ВАШЕ ИМЯ — ваше имя латиницей в кавычках, вместо ВАША ПОЧТА — любой адрес, его никто не проверяет.

3. Первый коммит — всё, что сделано к этому моменту:

▶ Выполните
git add .
git commit -m "день 11: свои функции"
git log --oneline

4. Соберите программу, спрячьте собранное правилом в .gitignore и сделайте второй коммит. Файл .gitignore можно создать и в VS Code — одна строка prog:

▶ Выполните
cd ~/gocourse/day11/cost
go build -o prog .
cd ~/gocourse/day11
git status -s
printf 'prog\n' > .gitignore
git check-ignore -v cost/prog
git add .
git commit -m "не хранить собранные файлы"

5. Работа для третьего коммита: в cost/main.go цена, порог и цена партии написаны числами прямо внутри функции. Дайте им имена — константами из дня 02, рядом с функцией:

Код для чтения · разбираем, набирать не нужно
const pricePerItem = ЦЕНА
const bulkFrom = ПОРОГ
const bulkPrice = ЦЕНА_ПАРТИИ

Вместо ЦЕНА, ПОРОГ и ЦЕНА_ПАРТИИ — три числа из вашего cost/TASK.txt; в теле cost числа заменяются на эти имена. Вывод программы от этого не меняется — проверьте запуском, а потом посмотрите изменения и запишите их:

▶ Выполните
cd ~/gocourse/day11/cost
gofmt -w .
go run . < example.txt
cd ~/gocourse/day11
git status
git diff
git add .
git commit -m "cost: имена цене и порогу"
git log --oneline

Готово, когда: git log --oneline показывает не меньше трёх строк, git status говорит working tree clean, а git check-ignore -v cost/prog печатает строку из .gitignore — пункты git, gitignore, t_gitdiff и t_log.


Что может пойти не так

Что видите Что это значит Что делать
Author identity unknown и fatal: unable to auto-detect email address не задана подпись автора git config --global user.name и user.email
fatal: not a git repository (or any of the parent directories): .git команда выполнена не в папке репозитория cd ~/gocourse/day11
nothing added to commit but untracked files present файлы есть, но не отложены в индекс git add ., потом git commit -m "…"
nothing to commit, working tree clean при попытке коммита всё уже записано в историю это не ошибка: сохранять нечего
git diff ничего не печатает, хотя вы правили файл изменения уже отложены командой git add git diff --staged
git log открылся и не закрывается вывод показывается постранично q для выхода; --oneline короче
редактор открылся и ждёт текста git commit без -m закрыть редактор и повторить с -m "сообщение"
?? cost/prog в git status -s собранная программа не спрятана строка prog в .gitignore
исходник перестал показываться в status правило в .gitignore слишком широкое правило должно называть собранный файл, а не папку
файл уже в истории, а .gitignore его не убирает правила действуют на новые файлы, а не на уже добавленные git rm --cached путь, потом коммит

Материал дня закончен. Задания — course lab 11, дополнительное чтение — course extra 11