一些概念
- 工作区:指放项目代码的地方;
- 暂存区:用于临时存放改动;
- 版本库:项目目录下的.git目录,存放了提交到所有版本的数据;
工作区下的文件状态:
- 已跟踪:指那些被纳入了版本控制的文件,也就是代表 git 已经知道的文件。
- 未跟踪:既不存在于上次快照的记录中,也没有被放入暂存区的文件。通过执行git add,将其变为已跟踪。
项目初始化为git仓库
// 将当前工作目录初始化为git仓库, 初始化完成后会在当前项目下生成.git目录
git init
克隆远程仓库到本地
git clone <repo-url>
配置提交用户信息
// 设置提交时的用户名
git config --global user.name "name"
// 设置提交时的邮箱
git config --global user.email "email"
将本地仓库关联远程仓库
git remote add origin <remote-repo-url>
解除本地仓库当前关联的远程仓库
git remote rm <remote-repo-url>
配置ssh
设置ssh密钥的目的是为了节省输入用户名密码的过程,同时保证传输安全。并不是必须设置。
git add
// 将filename 文件提交到暂存区
git add <filename>
// 将所有工作区的改动内容都保存到暂存区
git add .
将本地修改内容保存到暂存区。也是告诉git下一次commit 的时候需要将哪些改动提交。
git commit
将暂存区的内容保存到版本库当中,产生一个hash,共40位。其当于是一份快照,包含了这一次的修改内容。commit 常用命令:
| 命令 | 描述 |
|---|---|
| git commit -m 'msg' | 执行完git add之后,使用该命令将暂存区的内容存放到版本库 |
| git commit -am 'msg' | 不用执行git add, 可直接将工作区的修改保存到版本库。(如果有未跟踪的文件,那么还是需要执行git add) |
| git commit --amend | 进入编辑状态,可修改commit msg |
git status
用于查看工作区和暂存区的改动内容。可以查看到哪些修改被暂存了,哪些文件没有被跟踪。一般在进行任何操作之前都可以使用该命令查看一下状态,避免提交错误内容。如果修改已经被commit,git status不显示已经commit到项目历史中去的信息。
git log
查看提交历史。执行该命令后可以看到每次commit产生的hash、commit msg、用户信息等:
git reflog
git cat-file
显示的内容/类型等信息。
git diff
用于查看文件与上次commit时的差异。
git merge
将当前分支合并到指定分支,合并后产生一个新的commit,通过这个commit将两个分支的历史联系起来,当前分支指向这个新的commit。
如下图,main分支为当前分支,在main分支上执行完git merge dev后,产生了一个新的commit c4, c4具有两个父节点c2和c3,main分支指向新的commit c4。
git rebase
rebase 有两个常见功能,一是合并代码,二是合并commit。
- 合并代码
将当前分支移植到指定分支或指定commit之上,是一个线性的提交记录,会调整合并的顺序。
如下图,执行git rebase main后,这个命令会把dev分支里的每个提交(c2)都取消掉,并且把它们临时保存为补丁(patch),这放到.git/rebase目录中,然后把dev分支更新为最新的main分支,最后把保存的这些补丁应用到dev分支上。
当dev分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection),这些被丢弃的提交会被删除。
- 合并commit
如果一个功能在开发阶段进行了多次commit,为了让提交日志看起来更加清晰,一般推荐在合并到主分支之前将多个commit合并成一个。语法:
git rebase -i <commitHash>
其中i表示区间,取值如下:
- pick:要保留的commit
- reword:保留commit,但是重新编辑commit的message
- squash:将当前commit与前一个commit合并
- drop:删除commit
常用命令:
| 命令 | 描述 |
|---|---|
| git rebase --continue | 当rebase有冲突的时候,解决完冲突后继续执行rebase |
| git rebase --abort | 中断rebase,回到rebase之前的commit |
git cherry-pick
可理解为挑选提交。选择指定的commit,合并到当前分支。语法为git cherry-pick <commitHash>。
eg:将dev分支的最后一个提交合并到master分支,就只需要在master分支执行git cherry-pick <commit-hash>,commitHash为dev分支的最后一个commit hash。就可以将该提交合并到master分支了。
常用操作
| 命令 | 描述 |
|---|---|
| git cherry-pick --continue | 当rebase有冲突的时候,解决完冲突后继续执行rebase |
| git cherry-pick --abort | 中断rebase,会会到rebase之前的commit |
git merge、git rebase、git cherry-pick比较
| 命令 | 是否会产生新的commit | 使用场景 | 优点 |
|---|---|---|---|
| git merge | 新增一个merge commit,然后将两个分支的历史联系起来 | 1. 主分支合并子分支; 2. 不想打乱提交的顺序 | 现有分支不会以任何方式被更改 |
| git rebase | 不会 | 子分支合并主分支 | 历史记录更加清晰,是在原有提交的基础上将差异内容反映进去,消除了 git merge所需的不必要的合并提交 |
| git cherry-pick | 不会 | 合并指定的commit | 可以指定某些commit提交,灵活 |
git fetch
将远程仓库中的所有提交拉取到本地,但不会合并到本地分支中。这个过程主要做两件事:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针
常用命令:
| 命令 | 描述 |
|---|---|
| git fetch | 拉取远程仓库所有提交记录到本地 |
| git fetch origin :side | 从远程拉取,如果本地没有side分支, 会在本地创建 |
| git fetch origin foo | 从远程仓库的foo分支上,获取所有本地不存在的提交, 放到本地的o/foo上 |
git pull
拉取远程某个分支的更新,再与本地的指定分支合并,即:
git pull = git fetch + git merge
因为要与本地的内容进行合并,可能会产生冲突,需要手动解决。常用命令:
| 命令 | 描述 |
|---|---|
| git pull | 当前分支自动与唯一一个追踪分支进行合并 |
| git pull origin | 本地的当前分支自动与对应的 origin 主机 “追踪分支” 进行合并 |
| git pull origin next | 取回 origin/next 分支,再与当前分支合并 |
| git pull origin next:master | 取回 origin 主机的 next 分支,与本地的 master 分支合并 |
git push
将本地仓库修改内容推送到远程仓库。
git revert
撤销之前的某个commit,将需要revert的版本的内容再反向修改回去,会生成一个新的commit。被撤销的那个commit会保留,且不会影响被撤销的commit之前和之后的提交内容。语法git revert <commitID>。
git reset
用于回退当前分支的版本,可以指定退回某一次提交的版本。有以下三种工作模式:
--hard: 改变工作区和暂存区到指定commit。--soft: 不改变工作区和暂存区,只移动HEAD到指定commit。--mixed: 只改变暂存区,不改变工作区。这是默认参数,通常用于撤销git add。
常用命令:
| 命令 | 适用场景 |
|---|---|
| git checkout . | 修改完还未git add |
| git reset --hard | git add提交还未commit |
| git reset --hard 3d82ab | 已经git commit还未git push |
| git reset --hard commitId | 回退到任意版本 |
revert 和reset 比较
| 命令 | 区别 | 使用场景 |
|---|---|---|
| git revert | 1. 用一次新的commit来回滚之前的commit 2. HEAD指针继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容 | 1. revert 是创建一个新的提交来抵消更改,因此如果要恢复的提交已经被推送到共享仓库,则可以使用 revert来进行撤销。2. 如果要回退的版本之后的提交还需要,则可以使用revert |
| git reset | 1. 直接删除指定的commit 2. 将HEAD指针向后移动了 | 确认在需要回退的版本之后的提交都可以不需要的时候 |
git checkout
git checkout 主要有四个功能:
- 切换分支
- 切换到指定commit
- 切换到某个tag
- 撤销工作区的文件修改
常用命令:
| 命令 | 描述 |
|---|---|
| git checkout branchName | 切换到branchName分支 |
| git checkout | 切换到指定hash |
| git checkout tags/1.1.0 | 切换到某个tag |
| git checkout -b branchName | 创建一个新的分支并切换 |
| git checkout -- <file.name> | 撤销工作区的文件修改 |
分支操作
每个分支指向某个提交记录。当切换分支时,git会使用该分支的最后一次commit hash替换当前工作区的内容。
| 命令 | 描述 |
|---|---|
git branch | 显示本地所有分支 |
git branch -a | 显示所有远程分支 |
git branch -m <new-branch-name> | 修改当前分支名称 |
git branch <branch-name> | 创建branchName分支 |
git checkout <branch-name> | 切换到branchName分支 |
git checkout -b <branch-name> | 基于当前分支创建branchName分支,并切换 |
git branch -D <branch-name> | 删除本地分支(要删除的分支不能是当前所在的分支) |
git push origin --delete <branch name> | 删除远程分支 |
git tag
git仓库的tag是git版本库的一个标记,指向某个commitHash标记的快照记录指针。所以,标签也是版本库的一个快照。tag和branch的区别:
- tag对应某次commit节点, 是一个点,不可移动;
- branch对应一系列commit,是很多点连成的一根线,有一个HEAD 指针,是可以依靠 HEAD 指针移动的。 所以,两者的区别决定了使用方式,改动代码用branch ,不改动只查看用 tag。
| 命令 | 描述 |
|---|---|
| git tag | 创建轻量标签 |
| git tag -a tagName -m "released [VERSION]" | 创建带注解的标签 |
使用场景: 已经发布了 v1.0 v2.0 v3.0 三个版本,这个时候,想在不改现有代码的前提下,在 v2.0 的基础上加个新功能,作为 v4.0 发布。就可以从v2.0 的代码作为一个 branch ,然后作为开发分支。
git stash
用于暂时保存没有提交的修改。运行该命令后,所有被跟踪的修改内容,都会暂时从工作区移除,回到上次commit时的状态。
常用命令:
| 命令 | 描述 |
|---|---|
| git stash | 暂存当前修改内容 |
| git stash pop | 恢复最近一次的stash |
| git stash list | 显示所有暂存记录 |
| git stash drop | 丢弃最近的一次暂存 |
| git stash clear | 清除所有的暂存记录 |
使用场景:在当前分支对代码进行了修改,还没有准备好对其进行提交,但需要切换到其它分支去进行代码修改的情况。
常见问题
出现nothing added to commit but untracked files present提示?
这是因为在git commit时工作区存在未被跟踪的文件。需要先执行git add <file>让其变为已追踪状态, 再执行git commit。
将一个分支上的多个commit 合并成一个详细步骤
首先找到需要合并的起始commitHash,如想合并hash为14b6和fa26的两次提交,那么合并的起始hash就为3d54:
执行git rebase -i 3d54后,进入编辑模式,将第二行pick修改为squash后退出编辑模式
此时出现编辑提交是的message,提交信息修改为一条,如下:
在使用git log 查看, 已经只剩下一个commit 了:
使用git stash ,发现有文件没有被暂存?
只有被跟踪的文件才能够被暂存。
当一个需要被.gitignore忽略的文件,不小心提交到了远程仓库,如何删除远程仓库中的该文件?
先执行git rm --cached <文件路径>, 再将该文件加入.gitignore文件中,最后git添加并推送到远程仓库。
回退到了某个版本,但是又想恢复到新版本怎么办?
- 首先通过
git reflog查看需要恢复的版本的引用; - 找到后执行
git reset HEAD@{12},就回到HEAD@{12}版本了。
如何修改本地仓库关联的远程仓库url?
// 先解除本地仓库关联的远程仓库
git remote rm <remote-repo-url>
// 为本地仓库关联新的远程仓库
git remote add origin <remote-repo-url>
使用rebase操作后,发现合并冲突,如何解决?
先手动解决完冲突的内容后,执行git add .,再执行git rebase --continue,然后push即可。
fatal:refusing to merge unrelated histories 拒绝合并不相关的历史?
A:在要执行的命令后面加上–allow-unrelated-histories参数。
Q:mac电脑初始化git 项目之后出现.DS_Store文件,怎么删除?
A:在终端执行下面命令:
find . -name .DS_Store -print0 | xargs -0 git rm -f --ignore-unmatch
在当前分支(dev)开发时,需要临时切换到别的分支(fix/bug1)修改bug, 但是又不想commit,怎么处理?
// 在dev分支将改动暂存(如果有未被跟踪的文件,需要通过git add 将文件变为已追踪状态)
git stash
// 切换到别的分支
git checkout fix/bug1
// 切换到dev分支,并取出暂存
git checkout dev
git stash pop
修改分支名称并同步到远程仓库
git branch -m oldBranchName newBranchName
// 删除远程分支
git push --delete origin oldName
// 上传新命名的本地分支
git push origin newName
// 将本地分支和远程分支关联
git branch --set-upstream-to origin/newName
删除指定的commit
// 1. 查看commit记录,找到要删除的commit 的前一个commit hash
git log
// 2. commit hash 为上一步找到的
git rebase -i <commit hash>
// 3. 将要删除的commit hash前面的pick 改为drop
推荐一个在线练习git的网站: learngitbranching.js.org/?locale=zh_…