用 Git 记下第一个版本:安装、配置与提交
到第二篇结束,学习目录里有 main.py、task_server.py、check_project.py、http_demo.py 几个文件,都靠你自己记着。改错一行想退回去,只能凭印象;改了三处,回头分不清哪些是这次的改动。
本节把它们放进版本控制:装好 Git,把第一版存下来。
本节结束时,你能把项目初始化成 Git 仓库、完成第一次提交,并用 git status 和 git log 随时回答「现在处于什么状态、之前存过什么」。
版本控制解决什么
三个每天都会遇到的情况:
- 改坏了:想退回上一个能跑的版本,但已经记不清改了哪几行。
- 改多了:一次做了三件事(改端口、加校验、调整输出),出了问题不知道该退哪一件。
- 换个地方:想把项目拷到另一台电脑或发给别人,不知道哪些文件该带、哪些是运行产生的垃圾。
Git的答案是:把「有意保存的一版」记下来,每一版都能查、能对比、能退回。一次保存叫一次提交(commit),提交里存的是这一次的全部改动和一句说明。
安装并确认
Windows 到 git-scm.com 下载安装,一路默认选项即可;装完开始菜单里会多一个 Git Bash(本教程继续用 PowerShell,不影响)。macOS 执行 git --version,系统会提示安装命令行工具,或者用 brew install git。Linux 用发行版包管理器,例如 sudo apt install git。
git --versiongit version 2.35.1.windows.2版本号和你机器上的不一样很正常,能打印出来就说明装好了。
告诉 Git 你是谁
每一次提交都会记下作者,所以第一次用之前要配置一次(只需要一次):
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"两条命令都没有输出,就是成功了。--global 表示这台电脑上所有仓库都用这个身份。检查一下:
git config user.name
git config user.email青戈
dev@example.com名字和邮箱会写进每一条提交,推给别人后别人能看到——用你愿意公开的名字。
把目录变成仓库
在学习目录执行:
git init
git branch -m mainInitialized empty Git repository in D:\Code\...\python-foundations\.git\第二个命令没有输出:它把默认分支名统一成 main(Git 2.28 之前的版本默认叫 master,不同机器可能不一样,这里明确改一下)。
git init 在目录里建了一个隐藏的 .git 目录,所有历史都存在里面。别手动删它——删掉就等于把所有历史清了。项目里的其他文件完全不受影响,它们还是原样躺在那儿。
看清状态:git status
git statusOn branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
check_project.py
http_demo.py
main.py
task_server.py
tasks.json
nothing added to commit but untracked files present (use "git add" to track)Git 把文件分成三种状态:
| 状态 | 含义 | 怎么变过来 |
|---|---|---|
| 未跟踪 | Git 还没开始管这个文件 | git add 文件 |
| 已修改 | 已跟踪的文件被改了,改动还没进这次提交 | git add 文件 |
| 已暂存 | 改动已放进「下次要提交的内容」 | git commit |
「已暂存」是 Git 的关键设计:git add 是挑选,git commit 才是保存。中间这一步让你能在一个提交里只放想放的东西。
第一次提交
先只加四个代码文件:
git add main.py task_server.py check_project.py http_demo.py
git status --shortwarning: LF will be replaced by CRLF in check_project.py.
The file will have its original line endings in your working directory
...(每个文件一行)
A check_project.py
A http_demo.py
A main.py
A task_server.py
?? tasks.json--short 是简短格式,两个字符分别是暂存区和工作区的状态:A 是新增、M 是修改、?? 是未跟踪。tasks.json 没进去——它是程序运行产生的数据,不是代码,第 4 节会讲怎么让它永远不进仓库。
警告里说的 LF / CRLF 是换行符的事,见后面的常见错误。
提交:
git commit -m "任务管理程序:本地增删改查、远端同步与验收脚本"[main (root-commit) 9122f58] 任务管理程序:本地增删改查、远端同步与验收脚本
4 files changed, 481 insertions(+)
create mode 100644 check_project.py
create mode 100644 http_demo.py
create mode 100644 main.py
create mode 100644 task_server.pyroot-commit表示这是第一个提交(没有上一个版本)。9122f58是这次提交的编号前 7 位,后面查历史、退回都用得上。-m后面那句话是提交信息:写这次改了什么。写「更新」「修改」等于没写,三天后的自己也看不懂。
提交之后:
git log --oneline
git status9122f58 任务管理程序:本地增删改查、远端同步与验收脚本
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
tasks.json
nothing added to commit but untracked files present (use "git add" to track)git log --oneline 一行一次提交,从新到旧;git status 里代码文件已经从列表里消失,说明它们进了版本库、工作区也干净了。
完整示例
在学习目录按顺序执行:
git --version
git config user.name
git config user.email
git init
git branch -m main
git status
git add main.py task_server.py check_project.py http_demo.py
git commit -m "任务管理程序:本地增删改查、远端同步与验收脚本"
git log --oneline预期结果
git log --oneline 打印出一行:编号 + 你写的提交信息。git status 里只剩 tasks.json 一个未跟踪文件——它没进仓库,这是本节刻意留下的,第 4 节处理。
常见错误
Author identity unknown / *** Please tell me who you are.:没配置身份就提交,Git 拒绝并给出三行提示:
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.按提示跑那两条 git config --global 再提交即可。这是新机器上最常见的第一步。
Windows 上 warning: LF will be replaced by CRLF in ...:不是错误,提交照常完成。原因是文件里用的是 LF 换行(Unix 习惯),Windows 的 Git 默认在检出时转成 CRLF。你可以不管它;如果想让它闭嘴并且让团队里所有人的换行一致,在项目根目录加一个 .gitattributes,写一行 * text=auto eol=lf。
fatal: not a git repository (or any of the parent directories): .git:命令在仓库外面执行了。Git 认的是当前目录往上的 .git,先 cd 到项目目录。
提交信息写错了、或者漏了一个文件:还没推给别人的话,git commit --amend -m "新的信息" 可以改掉最近一次提交;漏了文件就先 git add 再 --amend --no-edit 合进上一次。
以为 git init 会把文件上传到网上:init 只在本地建仓库,不联网、不注册账号。上传是第 6 节的事。
小结
- 第一次用 Git 先配
user.name和user.email,否则提交会被拒绝。 git init建仓库,git branch -m main统一分支名;历史都在隐藏的.git目录里。git status回答「现在什么状态」,git add挑选要提交的改动,git commit -m "..."保存成一版。- 提交信息写「这次改了什么」,它是给未来的自己看的。
- 程序运行产生的数据文件(如
tasks.json)不用提交,下一节之后会用.gitignore讲清楚。
练习
-
排查:同事(或另一台机器上的你)在新目录里跑
git commit -m "第一次提交",被拒绝并提示*** Please tell me who you are.。说明原因,并给出两条修复命令。 -
操作:把
PAGE_SIZE从5改成20,先用git diff看清改动,再提交。提交信息写清这次改了什么。 -
面试常问:
git add和git commit分别做什么?为什么要有「暂存」这一步,直接一次性提交不行吗?
验收:第 1 题能说出「每一步提交都要记作者,新机器/新身份没配过」,修复是 git config --global user.name 与 user.email(不加 --global 则只对这个仓库生效);第 2 题提交后 git log --oneline 顶部是你的新提交,git status 里 main.py 不再出现;第 3 题能说出「add 挑选进这次提交的改动,commit 保存」以及暂存带来的两个好处(一次提交只放一件事、提交前能再看一遍)。
查看提示
第 1 题:想想这个提示为什么出现在「提交」而不是「init」这一步。第 2 题:改完先别急,git diff 看的是工作区与上次提交的差别。第 3 题:回顾本节「已暂存」那一段和状态表。
查看参考答案
第 1 题:git init 只建仓库、不看作者,所以不报错;git commit 要把「谁在什么时候保存了这一版」写进历史,缺作者就拒绝:
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.修法是按提示配一次,之后这台机器上所有仓库都不用再配:
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"不加 --global 就是只给当前仓库配(比如公司仓库用工作邮箱、私人项目用另一个邮箱时)。
第 2 题:
git diffdiff --git a/main.py b/main.py
index a526183..871e31d 100644
--- a/main.py
+++ b/main.py
@@ -9,7 +9,7 @@ from pathlib import Path
REMOTE_ENV = "TASKS_API"
TOKEN_ENV = "TASKS_TOKEN"
DEFAULT_REMOTE = "http://127.0.0.1:9000"
-PAGE_SIZE = 5
+PAGE_SIZE = 20
def validate_tasks(value):- 是旧内容,+ 是新内容;@@ -9,7 +9,7 @@ 表示这一块在旧文件第 9 行起、新文件第 9 行起,上下文各带三行。确认这就是你想提交的改动,再:
git add main.py
git commit -m "每次取回任务时多取一些:PAGE_SIZE 5 → 20"[main b96e75e] 每次取回任务时多取一些:PAGE_SIZE 5 → 20
1 file changed, 1 insertion(+), 1 deletion(-)第 3 题:git add 把改动放进暂存区(下一次提交要包含的内容),git commit 把暂存区里的内容存成一个版本。中间这一步看起来多余,但它带来两个实际好处:
- 一次提交只放一件事:改端口和改校验是两件事,可以分两次提交,将来要回退、要 review、要查「这个改动是哪次进来的」都好办。
- 提交前能再看一遍:
git add之后git diff --staged是「这次将要提交的全部内容」,比对着整个工作区看改动更清楚;顺手就能发现自己多改了东西。
一次性提交等于把「保存」和「挑选」合成一步,省事但把判断权交给了默认行为——比如 git add . 会把临时文件、密钥文件一起带进去(第 4 节会讲这些怎么挡)。