原文地址:https://www.cloudcrossing.xyz/post/46/
1 工作流
本地仓库
本地仓库由git维护的三棵“树”组成。Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD


- 工作区,它持有实际文件,就是克隆项目到本地后,项目所在的文件夹目录。
- 工作区有一个隐藏目录 .git,这个不算工作区,而是Git的版本库(当前仓库下,如果没有任何的提交,那么版本库就是对应上次提交后的内容)。其包含两部分
- 暂存区(Index),它像个缓存区域,临时保存你的改动。用于存储工作区中添加上来的变更(新增、修改、删除)的文件的地方。操作时,使用 git add . 会将本地所有新增、变更、删除的文件的情况存入暂存区中
- HEAD指针,它指向你最后一次提交的结果。用于存储本地 工作区 和 暂存区 提交上来的变更(新增、修改、删除)过的文件的地方。操作时,使用 git commit –m "本次操作描述" 可以将添加到暂存区的修改的文件提交到当前分支中(因为我们创建Git版本库时,Git自动为我们创建了唯一 一个master分支,所以,现在,git commit就是往master分支上提交更改)。
当在空的工作区添加文件之后,执行 git add . 之后暂存区的状态如下

接着执行 git commit –m "本次操作描述" 就可以一次性把暂存区的所有修改提交到分支

远程仓库
这个地方就是我们搭建在服务器上的git远程仓库,也就是在项目开始开发前,每个人要下载项目到本地的地方
2 创建和克隆
创建新仓库
创建新文件夹并打开,执行 git init 以创建新的git仓库
克隆仓库
执行如下命令创建一个本地仓库的克隆版本
- git clone /path/to/repository
如果是远端服务器上的仓库
- git clone username@host:/path/to/repository
3 添加和提交
添加
提出更改(把它们添加到暂存区),使用如下命令
- git add <filename>
- git add *
提交
使用如下命令以实际提交改动
- git commit -m "代码提交信息"
现在,改动已经提交到了 HEAD,但是还没到远端仓库
4 查看本地仓库状态
4.1 查看本地工作区、暂存区中文件的修改状态
要查看查看本地工作区、暂存区中文件的修改状态,使用以下命令
- git status
若未修改文件,执行命令会出现下图提示

若在项目中修改和新建文件时,执行命令会出现下图提示

- 第一条绿色提示的前缀为 modified,表示修改XXX文件
- 第二条绿色提示的前缀为 newfile,表示新增XXX文件
- 第四条绿色提示的前缀为 deleted,表示删除XXX文件
- 黄色选框的部分提示 Changes to be committed 含义为以下文件已存入暂存区,在提交到本地仓库时会将这些变更提交到本地仓库中
- 蓝色选框的部分提示 Changes not staged for commit 含义为以下文件未存入暂存区,在使用 commit 命令进行提交操作时,若未使用 -a 参数的话,则以下文件不会提交到本地仓库中
- 有时候还会出现 Untracked files 的提示,其含义为未监视文件,表示以下文件还没有添加到暂存区中。但区别在于,提示 Untracked files 的文件,无法使用 git commit –am 命令将文件添加到本地仓库中
如何处理 Untracked files 呢?
可以使用 git clean 删除此类文件:
- 删除 untracked files:git clean -f
- 连 untracked 的目录也一起删掉:git clean -fd
- 连 gitignore 的 untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的):git clean -xfd
- 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删:git clean -nxfd、git clean -nf、git clean -nfd
4.2 查看文件修改的地方
查看文件修改的地方,可以使用以下命令
- git diff
顾名思义就是查看difference。用于显示提交和工作树等之间的更改。此命令比较的是工作区中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容
-
git diff --cached
- 暂存区和版本库(最后一次提交)之间的变化
- 查看已经 git add ,但没有git commit 的改动
-
git diff HEAD
- 可以查看工作区和版本库的差别
- 自上次提交以来工作树中的更改
- 如果运行“git commit -a”,查看将会提交什么
-
git diff -- <file>
- 比较当前文件和暂存区文件差异
5 显示提交日志信息
显示提交日志信息,可以使用以下命令
- git log
如果觉得显示的不好看,可以执行以下命令,设置别名
- git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
以后执行 git lg 就可以了

5.2 版本回退
首先,Git必须知道当前版本是哪个版本
- 在Git中,用 HEAD 表示当前版本,也就是最新的提交 8fdb07e
- 上一个版本就是 HEAD^,上上一个版本就是 HEAD^^
- 当然往上100个版本写100个 ^ 比较容易数不过来,所以写成 HEAD~100
现在把当前版本 second commit 回退到上一个版本 first,就可以使用
- git reset

- git lg

你会发现最新的版本 second commit 已经不见了,如果需要回到最新版本,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找到那个 second commit 的 commit id 是 8fdb07e,于是就可以指定最新版本

如果关掉了窗口,而且又忘掉了 second commit 版本的 commit id,可以使用命令查看执行过的每一次命令
- git reflog

Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的 HEAD 指针。当你回退版本的时候,Git 仅仅是把 HEAD 从指向 second commit

改为指向 first

然后顺便把工作区的文件更新了
5.3 管理修改
第一次修改 -> git add -> 第二次修改 -> git commit
Git 管理的是修改,当你用 git add 命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区
所以,git commit 只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交
5.4 撤销修改
第一种情况
当你在对工作区文件进行修改之后,准备提交。此时发现此次修改有误需要回复文件到上一个版本的状态,而且自己忘记哪里修改过了。这就可以使用 git status

你可以发现,Git会告诉你,使用 git checkout -- <file> 把 <file> 文件在工作区的修改全部撤销,这里有两种情况
- 一种是 <file> 文件自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态
- 一种是 <file> 文件已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态
- 总之,就是让这个文件回到最近一次 git commit 或 git add 时的状态
注意:git checkout -- <file>命令中的 -- 很重要,没有 --,就变成了“切换到另一个分支”的命令
第二种情况
如果要撤销修改的文件已经添加到暂存区之后(即 git add),git status 查看一下,修改只是添加到了暂存区,还没有提交

Git 同样告诉我们,用命令 git reset HEAD <file> 把暂存区的修改撤销掉(Unstage),重新放回工作区

再用 git status 查看一下,现在暂存区是干净的,工作区有修改

接着可以丢弃工作区的修改 git checkout -- <file>
第三种情况
如果已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退部分,不过前提是没有推送到远程仓库
第四种情况
如果删错了文件,可以很轻松地从版本库把误删的文件恢复到最新版本,因为删除也算是修改
6 推送改动
改动现在已经在本地仓库的 HEAD 中了。执行如下命令以将这些改动提交到远端仓库
- git push origin master
可以把 master 换成想要推送的任何分支
如果还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,可以使用如下命令添加
- git remote add origin <server>
如此就能够将你的改动推送到所添加的服务器上去了
7 分支管理
7.1 创建与合并分支
每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在 Git 里,这个分支叫主分支,即 master 分支
HEAD严格来说不是指向提交,而是指向master,master才是指向提交的。所以,HEAD指向的就是当前分支
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点

每次提交,master分支都会向前移动一步,这样,随着不断提交,master分支的线也越来越长
创建一个叫做 dev 的分支,并切换过去
- git checkout -b dev

Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上

git checkout 命令加上 -b 参数表示创建并切换,相当于以下两条命令
- git branch dev
- git checkout dev
查看当前分支
- git branch
此命令会列出所有分支,当前分支前面会标一个 * 号
提交到新分支
- git push origin dev
从现在开始,对工作区的修改和提交就是针对 dev 分支了,比如新提交一次后,
dev 指针往前移动一步,而 master 指针不变

切换回主分支
- git checkout master
切换回 master 分支后,再查看刚刚提交的文件,刚才添加的内容不见了!因为那个提交是在 dev 分支上,而 master 分支此刻的提交点并没有变

合并指定分支到当前分支
- git merge dev

注意到上面的 Fast-forward 信息,Git 告诉我们,这次合并是快进模式,也就是直接把 master 指向 dev 的当前提交,所以合并速度非常快

合并完成把新建的分支删掉
- git branch -d dev

删除后,查看 branch,就只剩下 master 分支了

如果需要删除未合并的分支
- git branch -D dev
7.2 解决冲突
现在创建一个新的分支 feature1 并在该分支上修改文件且提交(commit),然后切换到 master 分支,Git 会自动提示我们当前 master 分支和远程的 master 分支分叉

接着在 master 分支上修改文件且提交(commit),现在,master 分支和 feature1 分支各自都分别有新的提交,变成了这样

这种情况下,Git 无法执行快速合并,只能试图把各自的修改合并起来,但这种合并就可能会有冲突

Git 告诉我们,test1.txt 文件存在冲突,必须手动解决冲突后再提交。git status 也可以告诉我们冲突的文件

直接查看冲突文件 test1.txt 的内容

Git用 <<<<<<<,=======,>>>>>>> 标记出不同分支的内容
我们修改冲突处为 Creating a new branch is quick and simple. 之后 add 和 commit

现在,master 分支和 feature1 分支变成了下图所示,还可以使用 git lg 看到分支的合并情况

最后,删除 feature1 分支
7.3 分支管理策略
通常,合并分支时,如果可能,Git 会用 Fast forward 模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用 Fast forward 模式,Git 就会在 merge
时生成一个新的 commit ,这样,从分支历史上就可以看出分支信息。
创建并切换 dev 分支,修改 test1.txt 文件,并提交一个新的 commit 。然后切换回 master 。准备合并 dev 分支,请注意 --no-ff 参数,表示禁用 Fast forward

因为本次合并要创建一个新的 commit,所以加上 -m 参数,把 commit 描述写进去
合并后,我们用 git log 看看分支历史

可以看到,不使用Fast forward模式,merge后就像这样

合并分支时,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而 fast forward 合并就看不出来曾经做过合并
7.4 Bug分支
在 Git 中,由于分支是如此的强大,所以,每个 bug 都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除
当你接到一个修复一个代号101的 bug 的任务时,创建一个分支 issue-101 来修复它,但是当前正在 dev 上进行的工作还没有提交

工作只进行到一半,还没法提交,预计完成还需 1 天时间。但是,必须在两个小时内修复该 bug,利用 Git 提供的 git stash 功能。可以把当前工作现场 储藏 起来,等以后恢复现场后继续工作

用 git status 查看工作区,可以看到是干净的。现在确定好在哪个分支上修复 bug,假定需要在master分支上修复,就从 master 创建临时分支。修复完成后,切换到 master 分支,并完成合并,最后删除 issue-101 分支
回到 dev 分支继续开发,用 git stash list 命令看看刚刚存的工作现场,有两种方式恢复

- 一是用 git stash apply 恢复,但是恢复后,stash 内容并不删除,你需要用 git stash drop 来删除:git stash apply stash@{0}
- 二是用 git stash pop,恢复的同时把 stash 内容也删了

7.5 多人协作
当你从远程仓库克隆时,实际上 Git 自动把本地的 master 分支和远程的 master 分支对应起来了,并且,远程仓库的默认名称是 origin
查看远程仓库信息
-
git remote
-
查看更详细的信息
-
git remote -v

上面显示了可以抓取和推送的 origin 地址,如果没有推送权限,就看不到 push 的地址
注意:本地新建的分支如果不推送到远程仓库,对其他人就是不可见的
多人协作的工作模式通常为
- 1、首先,可以试图用 git push origin <branch-name> 推送自己的修改
- 2、如果推送失败,则因为远程分支比你的本地更新,需要先用 git pull 试图合并
- 3、如果合并有冲突,则解决冲突,并在本地提交
- 4、没有冲突或者解决掉冲突后,再用 git push origin <branch-name> 推送就能成功
如果 git pull 提示 no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令
- git branch --set-upstream-to <branch-name> origin/<branch-name>
8 更新
从远程获取最新版本并 merge 到本地,自动合并
-
git pull
-
取回 origin 主机的 next 分支,与本地的 master 分支合并
- git pull origin next:master
-
如果远程分支 (next) 要与当前分支合并,则冒号后面的部分可以省略
- git pull origin next
从远程获取最新版本到本地,不会自动合并
- git fetch
9 标签
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照
Git 的标签虽然是版本库的快照,但其实它就是指向某个 commit 的指针
跟分支很像对不对?但是分支可以移动,标签不能移动
创建标签
- 切换到需要打标签的分支上,然后执行
- git tag <tagname>
- 默认标签是打在最新提交的 commit 上的。你也可以找到历史提交的 commit id 来将标签打到指定 id 上
- git tag <tagname> <commit id>
- 创建带有说明的标签,用 -a指定标签名,-m 指定说明文字
- git tag -a <tagname> -m "info" <commit id>
- 可以使用命令查看所有标签
- git tag
- 注意,标签不是按时间顺序列出,而是按字母排序的。可以用 git show <tagname> 查看标签信息
操作标签
- 删除标签。创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除
- git tag -d <tagname>
- 推送某个标签到远程
- git push origin <tagname>
- 一次性推送全部尚未推送到远程的本地标签
- git push origin --tags
- 如果标签已经推送到远程,要删除远程标签
- 先从本地删除
- git tag -d <tagname>
- 然后,从远程删除
- git push origin :refs/tags/<tagname>
- 先从本地删除
10 日志
如果你想了解本地仓库的历史记录,最简单的命令就是使用
- git log
你可以添加一些参数来修改他的输出,从而得到自己想要的结果。 只看某一个人的提交记录
- git log --author=bob
一个压缩后的每一条提交记录只占一行的输出
- git log --pretty=oneline
或者你想通过 ASCII 艺术的树形结构来展示所有的分支, 每个分支都标示了他的名字和标签
- git log --graph --oneline --decorate --all
看看哪些文件改变了
- git log --name-status
这些只是你可以使用的参数中很小的一部分。更多的信息,参考
- git log --help
11 替换本地改动
假如你操作失误(当然,这最好永远不要发生),你可以使用如下命令替换掉本地改动
- git checkout -- <filename>
此命令会使用 HEAD 中的最新内容替换掉你的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响
假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它
- git fetch origin
- git reset --hard origin/master
12 贴士
实用小贴士
内建的图形化 git:gitk
彩色的 git 输出:git config color.ui true
显示历史记录时,每个提交的信息只显示一行:git config format.pretty oneline
交互式添加文件到暂存区:git add -i
网友评论