Git是一个开源的分布式版本控制系统,而GitHub是开源代码托管平台,它提供基于Git的代码托管服务 你也可以自己搭建和管理Git服务器来进行代码库的管理,或者选择别的代码托管平台,如:GitLab
使用 SSH 的方式进行克隆,将使得我们本地与 GitHub 之间建立了信任连接,也就意味着之后所有需要进行用户认证的地方都不再需要显式地用户名密码认证。
git clone [-b 分支名] [--single-branch] 仓库地址 [local_path] 例子:git clone -b dev --single-branch xxx demo 将远程仓库xxx的dev分支克隆到本地,目录名字为demo --single-branch表示只克隆特定分支到本地,如果不加则将远端代码库全部克隆到本地,一般不加
在克隆了代码库之后,我们一般仍需要对 Git 做一些基本的配置才能使用 Git 进行日常工作。 Git 配置的作用域主要有三种:System、Global 和 Local,分别对应的配置文件地址为:
System:/etc/gitconfig。系统级别有效。Global:home 目录下的 ~/.gitconfig 文件。用户级别有效。Local:代码库目录的 .git/config 文件。代码库级别有效。另外我们也可以使用 git config --system -l,git config --global -l,git config --local -l 命令分别列出三个作用域下的配置。 在使用 git config 命令进行配置的时候,也可以使用 git config --system,git config --global,git config --local 三种不同的选项来修改不同作用域的配置。
配置 user 信息在 Git 中是十分重要的一个步骤, username 和 email 不能为空,它们将会被记录在每一条该 user 的 commit 信息中 我们可以配置user.name 和 user.email的值来配置 user 信息 git config --global user.name "caozhi" git config --global user.email "caozhi0321@gmail.com" 配置成功后可以使用 git config --global -l 命令查看配置
Git 也可以对不需要被代码库所管理的文件或文件类型进行配置,使得提交代码时,这些文件不会被提交到代码库中。Git 是通过忽略清单.gitignore 文件进行配置的。
.gitignore 文件每行表示一个匹配模式(# 开头的行或者空行除外,# 用于注释)。它使用 glob 模式来进行匹配,glob 模式是一种简化的正则表达式,常用于来进行路径的模式匹配。我们可以在代码库的根目录或者任意子目录添加.gitignore 文件,特定目录下的.gitignore 文件使得忽略规则只在该目录及其子目录下有效。
每个目录下都可以放单独的 .gitignore 文件以控制子目录的忽略规则。
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表模板,可以在 https://github.com/github/gitignore 找到它。
一个文件在 Git 中被管理时有三种状态以及对应所处的三种工作区域,理解这一特性将有助于我们更好的理解 Git 的常用命令的原理。在随后的 Git 操作介绍中,也会经常提到文件的各种状态变化和所处的工作区域。
Git 中有三个工作区域与上述三种状态相对应
工作目录(Working Directory):工作目录是我们常用的使用或修改代码的目录,它可以从 Git 仓库目录中 checkout 出特定的分支或者版本来使用。在工作目录的修改如果未添加到暂存区,那么该修改仍处在已修改状态。暂存区域(Staging Area):当我们在工作目录中修改了文件,我们需要先将修改添加到暂存区。暂存区的修改就是已暂存状态。Git 仓库目录(.git directory):Git 仓库目录就是真正存储和管理代码库的目录。提交修改到代码库本质上就是将暂存区的修改提交(commit)到代码库中。处在 Git 仓库目录中的修改就是已提交状态。总结下来,一次完整的提交包含以下操作:
修改文件。将修改的文件保存到暂存区(git add/rm/mv)。将暂存区的文件提交(git commit)到代码库中。当然如果需要将本地代码库的修改同步到远程代码库中(例如 GitHub),还需要将本地修改 push 到远程。
列出本地所有的分支 git branch或者git branch -a
origin 实际上是 git 默认生成的一个仓库名称,在每次 clone 的时候 git 会生成一个 origin 仓库,该仓库是一个本地仓库,它指向其对应的远程仓库。 查看本地所有的仓库所指向的远程仓库 git remote -v 添加一个本地仓库 x 指向另一个远端仓库Xgit remote add x X
HEAD 针是指向当前工作分支中的最新的分支或者 commit。Git 通过 HEAD 知道当前工作分支指向的哪条 commit 上。
当我们完成了本地的代码提交,需要将本地的 commit 提交到远端,我们会使用 git push 命令。Push 操作实际上是先提交代码到本地的 remote/** 分支中,再将 remote/** 分支中的代码上传至对应的远端仓库。 当远端仓库的提交历史要超前于本地的 remote/** 提交历史,说明本地的 remote 分支并不是远端最新的分支,因此这种情况下 push 代码,Git 会提交失败并提示 fetch first 要求我们先进行同步
fetch 和 pull 操作都可以用来同步远端代码到本地。在多数开发者的实践中,可能更习惯使用 git pull 去同步远端代码到本地, 但是 git fetch 也可以用于同步远端代码到本地,那二者的区别是什么呢?
fetch 操作是将远端代码同步到本地仓库中的对应的 remote 分支,即我们执行 git fetch 操作时,它只会将远端代码同步到本地的 remote/**分支中,而本地已经 checkout 的分支是不会被同步的。pull 操作本质上是一条命令执行了两步操作,git fetch 和 git merge。执行一条 git pull 命令,首先它会先执行 fetch 操作将远端代码同步到本地的 remote 分支,然后它会执行 git merge 操作将本地的 remote 分支的 commits 合并到本地对应的已经 check out 的分支。这也是为什么在 pull 时常常会出现 merge 的冲突,这是在执行 merge 操作时,git 无法自动的完成 merge 操作而提示冲突。另一种经常出现的情况是,pull 会自动产生一条 merge 的 commit,这是因为本地工作分支出现了未提交的 commit,而在 pull 时 Git 能够自动完成合并,因此合并之后会生成一条 merge 的 commit。让 Git 自动为我们去生成这样的 merge commit 可能会打乱我们的提交历史,因此比较好的实践方式是先 git fetch 同步代码到本地 remote 分支再自己执行 git merge 来合并代码到本地工作分支,通过这种方式来代替 git pull 命令去同步代码。
下面列出了一次完成的提交流程:
总是先同步远端代码到本地:一个 Git 的最佳实践是,在每次正式提交代码前都先将远端最新代码同步到本地。同步代码使用 git pull 或者 git fetch & git merge。将本地修改提交到暂存区:使用 git add/rm/mv 命令将本地修改提交到暂存区中。此处需要注意,为了使 Git 能够完整的跟踪文件的历史,使用对应的 git rm/mv 命令去操作文件的删除、移动和复制,而不要使用操作系统本身的删除、移动和复制操作之后再进行 git add。将暂存区的修改提交到本地仓库:使用 git commit 命令将暂存区中的修改提交到本地代码库中。使用 git push 命令提交本地 commit 到远端。Log 命令用于查看代码库的提交历史。结合 log 命令提供的各种选项,可以帮助我们查看提交历史中有用的提交信息。
–oneline 选项:不显示详细信息,只列出 commit 的 id 和标题-p 选项:列出 commit 里的文件差异-number 选项:只列出 number 数的 commit 历史–name-only 选项:列出每条 commit 所修改的文件名。此选项只列出修改的文件名,不列出修改类型–name-status 选项:列出每条 commit 所修改的文件名和对应的修改类型–stat 选项:列出每条 commit 所修改的统计信息git status 是另一个常用的命令,用于查看当前分支的修改状态。当前分支没有任何修改时,执行 git status 命令会显示 working tree clean 有三种状态:已经提交到了暂存区;还在工作区未被提交到暂存区;新文件,这些文件没有被代码库所跟踪。
Diff 操作用于查看比较两个 commit 或者两个不同代码区域的文件异同。
git diff:默认比较工作区和暂存区–cached 选项:比较暂存区和代码库的差异在命令后面指定特定的文件名,也可以比较特定文件的差异比较操作是开发过程中最常用的操作之一,场景包括通过比较来查看本地修改了哪些代码,比较特定分支之间的代码,或者 Tag 与 Tag 之间、Tag 与分支之间的比较。 Git 中比较操作可以通过 diff 操作和 log 完成,diff 主要用于比较文件内容的差异,而 log 操作主要比较 commit 的差异。
Diff 命令的基本格式是 git diff 。其作用是相比 src,列出目标对象 dst 的差异。 Git 中 Tag 和分支本质上都是指向对应 commit 的指针。因此 Tag、分支、commit 三者之间可以很平滑的进行比较操作。 使用 git diff 也可以查看单个文件的差异
回滚(Rollback)操作指的是将已经提交到代码库的 commit 生成一个与对应 commit 完全相反的 commit,相当于是对目标 commit 进行一次代码修改的逆向操作。
在实际项目中,经常用于进行版本的回滚或对某些错误提交进行回滚。Git 中是使用 revert 命令进行回滚操作,它会生成一条新的反向 commit,同时保留目标 commit。
撤销操作指的是丢弃我们的代码修改。实际开发中撤销通常包含多种情况:
撤销未保存至暂存区的代码。撤销已保存至暂存区但是还未提交到代码库的代码。撤销已提交到本地代码库但还未 push 到远端进行同步的代码。撤销已提交到远端的代码。撤销未保存到暂存区的代码 git checkout – filepath 命令来进行撤销某个文件的修改 git reset – hard HEAD 命令来丢弃本地所有修改 即回到HEAD的状态
撤销已保存至暂存区但是还未提交到代码库的代码 git reset HEAD 将暂存的修改撤销到工作区
撤销已提交到本地代码库但还未 push 到远端进行同步的代码 git reset [–hard] commit_id 命令来操作 如果我们只是想保留修改,我们可以使用 git reset commit_id 命令来使得 HEAD 指针指向对应的 commit,这样在其之后 commit 的代码修改会被撤销到工作区
当我们不需要保留修改,而想要完全丢弃掉 commit 时,我们可以使用 git reset --hard commit_id 命令,这样对应 commit 之后的 commit 将会完全被丢弃
撤销已提交到远端的代码
而对于已经提交到远端的 commit,此时我们没有办法再使用 reset 命令撤销掉原先的 commit,即使在本地用 reset 进行了撤销,再进行同步拉取代码时,仍然会将远端的 commit 拉回本地。因此这种情况我们只有通过 revert 进行回滚。
由此我们也可以看到,revert 和 reset 命令都可以用于撤销 commit,它们最大的不同在于 revert 会生成一条与之前完全相反的 commit 同时保留原先的 commit,而 reset 则是抛弃掉原先的 commit。
Reset 命令本质上是重置工作区的 HEAD 指针使其指向对应位置,当重置 HEAD 指针之后,会将 HEAD 指针之后的 commit 丢弃掉从而也达到了撤销修改的目的。reset 命令有三个参数:
–soft 选项:重置 HEAD 之后,将重置 HEAD 之后的 commit 的代码变更还原到暂存区。–hard 选项:重置 HEAD 之后完全丢弃 HEAD 之后的代码。–mixed 选项:默认选项。重置 HEAD 之后,将重置 HEAD 之后的 commit 的代码变更还原到工作区。Fast-forward 是指快进合并,它是直接将 master 分支指针直接指向了 dev 分支的 commit,而并没有在 master 分支上产生新的 merge commit。
执行 git merge 命令时通过加上–no-ff 选项来禁止 Fast-forward。可以看到非快进合并模式下,git 会产生一条新的 merge commit。使用 Fast-forward 模式的好处是可以快速的进行合并且不会产生 merge commit,但其缺点在于它不会保留合并分支的信息,因此当合并分支被删除时,也就不知道对应的提交是来自于哪个分支。
有时候我们实际项目中在自己的开发分支上可能会提交很多跟业务意义关系不大的 commit,例如格式修改、删除空格、撤销前次提交等等,执行 git merge 操作时默认情况下会将合并分支上这些原始 commit 直接合并过来,在目标分支上保留了详细的提交历史,往往这些无意义的提交历史会导致主分支的历史显得杂乱。这种情况下我们可以使用 squash 选项将待合并的所以 commit 重新替换成一条新的 commit。 git merge --squash 合并分支名
除了使用 git merge 命令来合并分支之外,我们还可以通过 cherry-pick 命令来检出特定的一个或多个 commit 进行合并 git cherry-pick commitId
当在代码中解决了冲突之后,我们需要将修改后的代码重新使用 git add/rm/mv 提交到暂存区,并重新 commit 到代码库中。