1. 初始化仓库
初始化仓库有两种方式。一种是直接在本地构建,另外一种是克隆远程仓库。
- 本地构建:
git init [本地仓库路径] - 克隆远程仓库:
git clone <仓库地址>
2. 工作区域和文件状态
2.1 三种工作区域
- 工作区(Working Directory),其实就是我们所在的工作目录;
- 暂存区(Staging Area/Index),用于临时存在我们即将提交到本地仓库的内容:
.git/index; - 本地仓库(Local Repository),是存储代码和版本信息的主要区域:
.git/objects;
2.2 四种文件状态
- 未跟踪(Untrack),指该文件还未被git管理起来;
- 未修改(Unmodified),指该文件已经被git管理起来,自首次提交后还未被修改过;
- 已修改(Modified),文件被git管理且被修改过,但还没提交到暂存区;
- 已暂存(Staged),指文件已经保存到了暂存区;
在Vscode中,使用如下缩写对应文件状态:
- ??(Untracked):未追踪
- 不显示:未修改
- M(Modified):已修改
- A(Added):已添加暂存
- D(Deleted):已删除
- R(Renamed):重命名
- U(Updated):已更新未合并
可以看到,状态5、6和7是比较特殊的。5和6可以视为是已修改的特殊情况,而7则是在合并冲突时显示。当然有时U也会被用作U(Untracked);
2.3 二者的交互
- 文件初始状态:Untracked
- 命令
git add将文件添加到暂存区,状态变成Staged; - 命令
git commit将暂存区的文件提交到本地仓库,状态变成到Unmodified; - 提交过后的文件经过一次修改,状态变成Modified;
总结来看,Untracked是未被git add过的文件的初始状态;Unmodified是被git commit后的初始状态。
3. 内容添加与提交
3.1 添加到暂存区
git add <filename>将文件添加到暂存区。示例:
git add A.py # 添加具体文件 git add *.txt # 使用通配符添加文件 git add . # 使用相对路径添加文件夹(当前文件夹所有文件)git rm <filename>将文件从工作区和暂存区同步删除掉,避免手动删除本地文件后再用命令去同步暂存区的内容。等价于手动删除本地文件后,再使用git add 被删除文件;git restore <filename>取消指定文件的暂存状态。
3.2 提交到本地仓库
使用git commit将暂存区的内容提交到本地仓库,其中:
- 使用
-m参数,如git commit -m "first commit"; - 无参数默认进入Vim编辑界面以编辑提交信息。敲击
ESC键,显示:后输入wq退出界面; - 使用
-am参数,其中a表示git add .,即将当前工作区内容添加到暂存区,再将暂存区内容提交到本地仓库。
3.3 查看
git ls-files查看被Git管理的文件,可以使用-m查看已修改但未暂存的文件;git status查看工作区和暂存区文件状态git log查看仓库提交历史记录,可以使用--oneline参数来查看简洁的提交记录。git reflog,查看过去所有使仓库发生变化git变化
!Note\] 有些文档说 `git ls-files --cached`可以查看暂存区文件情况,实践证明这完全是扯淡。标准做法是使用 `git status`查看暂存区的情况。
4. 版本回退
4.1 内容回退
使用git reset进行版本回退。它有三种默认参数:
git reset --soft表示回退后保留当前工作区和暂存区的所有内容git reset --hard表示回退后丢弃当前工作区和暂存区的所有内容git reset --mixed表示回退后保留当前工作区的内容,但是丢弃暂存区的所有内容。默认使用该模式。
特殊符号:HEAD^表示上一个版本,例如:
git reset HEAD^
配合git log --oneline命令可以方便回到某一个版本的提交;如果发生误操作,可以使用git reflog查看具体的失误操作id并回退;
5. 差异比较
git diff可以用于查看不同工作区、暂存区和本地仓库、不同版本以及不同分支之间的差异。
- 不加任何参数,
git diff默认比较工作区与暂存区之间的差异。 git diff HEAD比较工作区和本地仓库(最新提交)之间的差异;git diff --cached比较暂存区和本地仓库的差异;git diff id1 id2是比较不同版本或分支的通用写法,表示比较id2相对id1的变化。
特别的,我们常用HEAD指代特定版本。它的用法是:
HEAD表示当前版本HEAD~或HEAD^表示前一个版本HEAD~3表示前3个版本
例如下述常见的命令,表示当前版本相对上一个版本的变化:
git diff HEAD~ HEAD
6. 忽略文件
显然我们不希望某些文件被Git管理,如临时文件,缓存等等。此时我们可以使用.gitignore文件,在里面列出这些文件或文件夹,匹配规则是标准的glob正则表达式;注意文件夹必须以/结尾,如config/;常见模版见:gitignore模版
特别的,如果某些不想被管理的文件已经被Git管理起来,我们希望将其从暂存区删除,可以使用如下命令:
git rm --cached 文件名git rm --cached -r 文件夹名
特别注意,--cached表示只是删除暂存区的对应文件内容;-r表示recursively递归,即递推删除指定文件夹下所有文件。
7. 同步本地仓库和远程仓库
7.1 仓库关联
- 如果仓库是
git clone下来的,那么这个克隆下来的本地仓库和远程仓库已经有了关联; - 如果仓库是
git init创建的,并且需要与远程仓库建立关联,则使用如下命令。例如: git remote add <仓库别名> <仓库链接>
可以使用如下命令可以查看本地仓库锁关联的远程仓库信息:
git remote -v
7.2 信息同步
git pull表示拉取,即将远程仓库的修改拉取到本地,完整命令如下: git pull <-u 远程仓库名> <远程分支名>:<本地分支名>git push表示推送,即将本地仓库的修改推送到远程仓库,完整命令如下: git push <远程仓库名> <本地分支名>:<远程分支名>
这两个命令都可以进一步添加其他参数。例如下述命令表示将本地仓库与远程别名为origin的仓库连接,并把本地仓库main分支的内容推送到远程仓库的main分支:
git push -u origin main:main
8. 分支
8.1 基本命令
- 查看当前仓库的所有分支:
git branch - 创建新分支:
git branch <分支名> - 切换分支:
git switch <目标分支> - 合并分支:
git merge <目标分支>;git rebase <目标分支> - 查看分支图:
git log --graph --oneline --decorate --all - 删除已合并的分支:
git branch -d <目标分支> - 强制删除分支:
git branch -D <目标分支>
!NOTE
- 虽然也可以用
git checkout <分支名>来切换分支,但这种方式是有歧义的。因为是git checkout XX的还可以用来恢复某个文件的意外修改。如果某一个文件的名字恰好和分支名相同,git checkout XX会优先选择切换到对应的分支而不是恢复文件,从而导致歧义。 - 在
git merge表示将目标分支合并到当前分支中。因此如果想将开发功能分支合并到主分支中,记得先切换回主分支,再执行merge命令。 - 合并分支后,目标分支并不会消失。如果确定被合并的分支不再需要了,可以使用第6条命令删除对应分支。
8.2 两种分支合并的方式
git merge和git rebase是合并分支的两种不同的方式。假设我们的主分支是main,开发分支是dev;如果我们想将dev分支合并到main分支的话:
git switch main;git merge dev在将dev分支内容合并后,只会在main分支上产生一个新的merge提交,不会破坏原有的分支结构。git switch main;git rebase dev在将dev分支内容合并后,会将main分支的提交历史和dev分支嫁接,具体解释是:
rebase意为"变基","基"意为共同的祖先。假设dev分支是在main第三次提交时创建的,那么第三次提交就是两个分支的共同祖先;- 命令会将
main分支第三次提交后的内容移动到dev分支的最新提交后面,相当于把main的共同祖先从第三次提交变成了dev的最新提交; - 这样
main分支中有了dev分支的完整提交历史;缺点是,这种方式破坏了main分支的实际提交顺序,也破坏了原有的分支结构。git switch dev;git rebase main同理;
在实际开发中,git merge则适合团队开发写作,避免破坏原有的分支结构和提交历史;git rebase则适合个人开发项目使用,可以将不同分支的提交历史合并成一条清晰的脉络;
9. 开发的工作流程
理解至上!详见视频: 十分钟学会正确的github工作流,和开源作者们使用同一套流程