Первый репозиторий: 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.gogit 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 cleanworking 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.goM — modified, файл изменён. ?? — git про такой файл ничего не знает.
Ритм работы получается такой, и он одинаковый у всех:
- что-то сделали и проверили запуском;
git status— что вообще изменилось;git diff— что именно изменилось в этих файлах;git add .— отложить;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 — список того, что не нужно хранить, по строке на правило:
.gitignoreprogОдна строка 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/progcost/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 --oneline4. Соберите программу, спрячьте собранное правилом в
.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