版本控制
即控制版本,帮助控制(或管理)某事物(通常是源代码)的不同版本。(保留项目的详细历史记录)
两大类型
- 集中式(所用用户都连接到一个中央的主仓库)
- 分布式(每个用户本地都拥有一个完整的仓库)
_Git_就是分布式的版本控制工具
_GitHub_则是托管Git项目的服务
关键术语
版本控制系统(VCS):管理源代码不同版本的工具;源代码管理器(SCM):版本控制系统的另一个名称。Git是一个SCM,也是一个VCS,它的网站是https://git-scm.com/。
提交(Commit):Git将数据看作微型文件系统中的一组快照。每次commit,都对文件当时的状态拍照,并保存该快照的引用。commit是Git中的基本单位。
仓库(Repository/repo):一个包含项目内容和几个文件(在Mac OS上默认处于隐藏状态)的目录,可以存储在本地,或者作为远程副本存储在其他计算机上,由commit构成。
工作目录(Working Directory):在计算机的文件系统中可以看到的文件,当在代码编辑器中打开项目文件时,正是在工作目录中处理文件。与这些文件形成对比的是保存在仓库里的文件。
检出(Checkout):将仓库中的内容复制到工作目录下。
暂存区/暂存索引(Staging Area/Staging Index):Git目录下的一个文件,存储即将进入下一个commit内容的信息。可将其看作准备工作台,Git将在此获取下一个commit。
SHA(Secure Hash Algorithm,安全哈希算法):由0~9、a~f组成的长度为40的字符串,根据Git中文件或目录结构的内容得出,是每个commit的ID编号,示例:e2adf8ae3e2e4ed40add75cc44cf9d0a869afeb6
。
分支(Branch):从主开发流程中分支出来的新的开发流程。这种分支开发流程可以在不更改主流程的情况下继续延伸下去。
Git命令
git init
在计算机上从头创建全新仓库。运行该命令会初始化Git跟踪所有内容会用到的所有必要文件和目录。所有这些文件存储在.git目录(在Mac/Linux上将是一个隐藏目录)下,该目录就是一个库,Git将所有commit记录在这里,并跟踪所有内容。.git目录下各项内容:
- config文件 - 存储与项目相关的配置信息
- description文件 - 用于GitWeb程序
- hooks目录 - 放置客户端或服务器脚本,以便用来连接到Git的不同生命周期事件
- info目录 - 包含全局排除文件
- objects目录 - 存储提交的所有commit
- refs目录 - 存储指向commit的指针(通常是“分支”和“标签”)
git clone
将一个现有仓库从其他地方克隆或复制到本地计算机上。该命令后传入要克隆的Git仓库的路径(通常是URL),示例:git clone https://github.com/udacity/course-git-blog-project
。默认创建一个与被克隆仓库名称相同的目录,也可以再提供一个参数作为该目录的名称。
git status
查看仓库当前状态。输出结构将有所不同,具体取决于文件是否被添加/删除/修改、暂存索引的情况,以及仓库的状态。
git log
显示有关所有commit的信息。默认情况下,显示每个commit的:
- SHA(commit ID,完整的、唯一的)
- 作者(author)
- 日期(date)
- 消息(commit message)
--oneline
选项用于更改git log
显示信息的方式:
- 每行显示一个commit
- 显示commit的SHA的前七个字符
- 显示commit的消息
--stat
(stat是statistics 统计信息的简称)选项用于更改git log
显示信息的方式:
- 显示被修改的文件
- 显示添加/删除的行数
- 显示摘要,其中包含被修改的总文件数和添加/删除的总行数
-p
(--patch
的简写)选项用于更改git log
显示信息的方式:
- 显示被修改的文件
- 显示添加/删除的行所在的位置
- 显示作出的实际更改
命令行分页器/less的使用:
- 要向下滚动,按:
j
- 移动一行d
- 移动半屏f
- 移动整屏
- 要向上滚动,按:
k
- 移动一行u
- 移动半屏b
- 移动整屏
- 要退出日志,按
q
git show
显示有关指定commit的信息。输出和git log -p
完全一样,默认情况下,显示:
- commit ID
- author
- date
- commit message
- patch info
git add
将文件从工作目录添加到暂存区。
- 可接受多个文件名(用空格分隔)
- 可使用
.
来代替文件列表,添加当前目录(包括所有嵌套文件和目录)
git commit
将文件从暂存区取出,并保存到仓库,将打开配置中指定的编辑器,在编辑器中:
- 必须在第一行输入提交说明
- 以
#
开头的行是注释,不会被记录 - 添加说明后保存文件
- 关闭编辑器以进行提交
-m
选项绕过编辑器。如果提交说明很简短,不想打开编辑器输入信息,可以直接使用该选项传入信息,示例:git commit -m "Initial commit"
,但是不能再为commit提供信息的描述了。
每个commit都有其_侧重点_,应该记录一项更改,这并不限制添加/删除多少行代码或添加/删除/修改多少个文件,一个commit不应该包含不相关的更改。
编写良好的提交说明,需要注意:
- 建议
- 消息篇幅简短(少于60个字符)
- 解释提交的作用
- 禁忌
- 不要解释为何、如何作出这些更改
- 不要使用“and”,如果使用了,则说明提交了太多的更改,应将这些更改拆分成独立的commit
如果需要解释为何进行了提交,可以第一行是消息本身,之后空出一行,然后输入正文/说明,包括为何commit的详情(例如URL)。最重要的是,保持一致性。
git diff
显示文件两个版本之间的差异,用来查看已被加入但尚未提交的更改。git log -p
在后台使用了该命令。
如果想将某个文件保留在项目的目录结构中,又要确保不被意外地提交到项目中去,可以使用名称特殊的文件.gitignore
(Git会查看其中内容)。将此文件添加到项目的根目录,并在其中列出Git忽略/不跟踪的文件名。
通配符允许使用特殊字符来表示某些格式/字符,在.gitignore
文件中可以使用:
- 空白行作为空格
#
- 将行标记为注释*
- 与0个/多个字符匹配?
- 与1个字符匹配[abc]
- 与a/b/c匹配**
- 与嵌套目录匹配 -a/**/z
与以下项匹配:- a/z
- a/b/z
- a/b/c/z
git tag
为特定提交添加标签。标签是提交的额外标记,可以指示有用的信息,比如v1.0。git tag -a v1.0
命令中的-a
选项告诉Git创建一个带注释的标签,将打开编辑器,并等待为标签输入注释信息。如果没有提供该选项,将创建一个轻量级标签。建议使用带注释的标签,因为它包含大量额外信息,如:
- 标签创建者
- 标签创建日期
- 标签信息
只输入git tag
时,命令行会显示仓库中所有的标签。标签与commit相绑定,想知道它在仓库中的具体位置时,可以使用git log
。
-d
选项删除标签,如:git tag -d v1.0
。
git tag -a v1.0 a87984
将向SHA为a87984
的commit添加标签v1.0
。借助这一技巧,可以为仓库中的任一commit添加标签。
git branch
与Git分支进行交互(列出、创建、删除)。用于并行开发项目的不同功能,不会对哪些提交属于哪些功能而感到困惑。
只输入git branch
,将列出仓库中所有分支。要创建分支,只需git branch
后面加上要创建的分支的名称(默认分支名为:master)。
git log
输出的结果中,“HEAD”具有一个指向_当前分支_的箭头。git branch
命令输出的结果中,_活跃分支_旁有一个“*”。
-d
选项告诉Git删掉给出的分支,示例:git branch -d sidebar
。注意,无法删除当前所在的分支。如果某个分支上有任何其他分支上都没有包含的commit(即此commit是要被删除的分支所独有的),Git不会删除该分支。要强制删除,需要使用-D
选项。
git checkout
在不同的分支和标签之间进行切换。
在进行commit时,该commit将添加到当前分支上。要在分支之间切换,需要使用git checkout
。该命令的工作方式:
- 从工作目录中删除Git跟踪的所有文件和目录(这些文件和目录都存储在仓库中,故不会丢失)
- 转到仓库,提取分支指向的commit所对应的所有文件和目录
添加-b
选项,能够创建分支并切换到该分支。示例:git checkout -b footer master
git log --oneline --graph --all
将显示仓库中所有分支和commit。--graph
选项将条目和行添加到输出的最左侧,显示实际的分支;--all
选项会显示仓库中所有的分支。
git merge
将不同分支的更改自动合并在一起。发生合并时,Git将:
- 查看将合并的分支
- 查看分支的历史记录并寻找两个分支的commit历史记录中都有的单个commit
- 将单个分支上更改的代码行合并到一起
- 提交一个commit来记录合并操作
合并时,将其他分支合并到当前(检出的)分支上,不是将两个分支合并到一个新的分支上,也不是将当前分支合并到其他分支上,另一分支上的更改将出现在当前(检出的)分支上。
快进合并(Fast-forward merge):将使当前(检出的)分支向前移动,直到它指向与另一个分支指向的commit一样为止。
普通合并:合并的是两个完全不一样的分支,将提交commit。在进行commit时,需要提供commit消息。因为这是合并commit,因此已经提供默认消息,可以更改此消息,但通常会直接使用默认消息(当编辑器被打开并包含此消息时,直接关闭以确认使用该commit消息)。
有时候Git无法完全自动地进行合并,合并失败时,就称合并冲突。Git将尝试尽可能合并多的内容,然后将留下特殊选项(例如>>>
和<<<
),告诉程序员需要从何处手动修复。
Git会跟踪文件中的代码行,如果相同的行在不同的分支上都被更改了,将产生合并冲突。
合并冲突指示符:
<<<<<<< HEAD
此行下方所有内容(直到下个指示符)显示当前分支上的行||||||| merged common ancestors
此行下方所有内容(直到下个指示符)显示原始的行=======
表示原始的行的结束位置,之后的所有行(直到下个指示符)是要被合并到当前分支上的行>>>>>>> heading-update
是要被合并分支(此例中是heading-update
)的行的结束位置
Git使用合并冲突指示符来告知两个不同分支上哪些行产生了合并冲突,以及原始的行是什么。要解决合并冲突,需要:
- 选择保留哪些行
- 删掉所有带指示符的行
之后,直接保存文件,并将其添加到暂存区,然后commit。就像普通合并一样,编辑器会弹出,让提供commit消息……
git commit --amend
更改最后一个commit,如果在提交中忘记包含某个文件或在提交说明中打错了字,就需要调用该命令。
git revert
向Git提供需要还原的commit的SHA,就能撤销在该提交中作出的更改,Git将创建新的commit,执行完全相反的更改。
git reset
删除提交,不能随意删除任意提交,必须按顺序删除。此命令存在潜在危险,它会从仓库中删除提交。
Git会在完全清除任何内容之前,持续跟踪大约30天。要调用这些内容,需要使用git reflog
命令。
可以使用特殊的“祖先引用”字符告诉Git相对引用:
^
- 表示父commit~
- 表示第一个父commit
引用之前的commit:
- 当前commit的父commit
- HEAD^
- HEAD~1
- HEAD~
- 当前commit的祖父commit
- HEAD^^
- HEAD~2
- 当前commit的曾祖父commit
- HEAD^^^
- HEAD~3
^
和~
的区别主要体现在通过合并而创建的commit中。合并commit具有两个父级,^
表示第一个父commit(运行git merge
所在的分支),^2
表示第二个父commit(被合并的分支)。
git reset <reference-to-commit>
:
- 将 HEAD 和当前分支指针移到目标 commit
- 清除commit
- 将commit的更改移到暂存区
- 取消暂存commit的更改
Git根据所使用选项来判断是清除、暂存之前commit的更改,还是取消暂存之前commit的更改。
如HEAD
指向9ec05ca
上的master
:
--mixed
选项 - 运行git reset --mixed HEAD^
会把commit9ec05ca
中做出的更改移至工作目录中--soft
选项 - 运行git reset --soft HEAD^
会把commit9ec05ca
中做出的更改直接移至暂存区--hard
选项 - 运行git reset --hard HEAD^
将清除commit9ec05ca
中做出的更改
在进行任何重置操作之前,通常在最近的commit上创建
backup
分支,如果出现错误,可以返回这些commit
小结
- Create repositores
- Make commits
- Review changes
- Develop on branches
- Merge branches and resolve conflicts
- Undo changes
git remote
管理远程仓库并与之交互
运行此命令时,如果尚未配置远程仓库,将不显示任何内容;如果克隆仓库后,将自动获得一个远程仓库。
origin
是远程仓库所在位置的简略表示,可以将它重命名为别的名字,但通常被命名为此名称。使用-v
选项,查看远程仓库的完整路径。
git remote add <name> <url>
将本地仓库与远程仓库建立连接
git push <remote-shortname> <branch>
将本地commits推送到远程仓库
注意:
- 可能需要输入用户名和密码,取决于如何配置
- 如果使用HTTP版本的远程仓库,就需要
- 如果使用SSH协议,并提供密钥,不需要
- 如果要输入用户名和密码,用户名会在输入后显示,但密码不会显示。只需继续输入密码,完成后按 Enter 键即可
- 如果输入密码出错,它会让重新输入
- Git会压缩文件使之变小,然后将其推送至远程仓库
跟踪分支的名称(origin/master
)包含远程仓库的简写名及分支名称,可在本地仓库跟踪远程仓库的信息,但并不能实时表现被跟踪分支在远程仓库上的位置(当其他人对远程仓库做了更改时)。
git pull <remote-shortname> <branch>
拉取远程仓库的更改
- 远程分支上的commit会被复制到本地仓库
- 本地跟踪分支(
origin/master
)移到指向最新的commit - 本地跟踪分支(
origin/master
)合并到本地分支(master
)
git fetch <remote-shortname> <branch>
用于从远程仓库分支检索commit,但不会在收到这些commit之后,自动将本地分支与远程跟踪分支合并。可以将 git fetch
想象成 git pull
它的一半操作,而 git pull
的另一半是合并。
使用git fetch
的一个主要情形是当远程分支和本地分支都拥有对方所没有的更改。要获取远程更改,将它们存储到本地分支中,然后手动执行合并。最后,可以将新的合并commit推送会远程仓库。
参考链接
https://cn.udacity.com/course/version-control-with-git–ud123