git特点:
1- 直接记录快照,而非差异比较。以快照流的方式对待数据。对所有文件制作一个快照,保存这个快照的索引。如果文件没有改动,git只是简单保留一个链接指向之前存储的文件。
2- 近乎所有操作都是本地执行,所以很快。例如svn如果断网了就无法提交。
git三种状态:
已提交(committed): 修改进入了本地数据库中
已修改(modified): 修改了,但未保存到数据库中
已暂存(staged): 将修改的文件快照放入暂存区等待提交
git三个区域概念(Working tree, staging area, and Git directory):
git仓库目录(.git):git用来存储项目元数据和对象数据库的地方
工作目录: git仓库的压缩数据库中提取出来的某个版本
暂存区: 是一个保存了待提交文件的信息,放在git仓库目录中
HEAD的概念:
HEAD就是指向当前分支的最后一个提交的指针
HEAD可以简写成@。 HEAD~N表示顺着自己这一脉(主线,git log --graph的左边)上溯N个提交。 HEAD^表示上溯一个提交(等效于HEAD^1,这里的1表示主线,2表示被合并的提交<假设存在,git log --graph的右分支>), HEAD^^^表示主线上溯3个提交(等效于HEAD^^^1)。
获取帮助:
git help <verb> #例如git help config
git <verb> --help
man git-<verb>
获取一个仓库(repository):
途径1: 克隆远程仓库。 执行git clone <remote_repo> [directory]会从服务器仓库上拉取源码文件的所有版本
途径2: 初始化一个本地仓库。在有源码的目录中执行git init命令初始化一个仓库,在目录下会生成.git仓库目录。此时没有任何文件被跟踪
基本流程:
1-修改文件
2- git add <pathspec>... # 跟踪<pathspec>指定的目录或文件,也就是将它们纳入版本控制。同时这些文件也进入了暂存区,等待提交。
3- git commit -m "初始化工程" #提交到本地git仓库(也就修改的快照进入.git仓库目录)
注意: git add 命令是个多功能命令。1-可以用它开始跟踪新文件; 2-把已跟踪的文件放到暂存区; 3-还能用于合并时把有冲突的文件标记为已解决状态...等功能。
git commit是将暂存区的快照提交到git仓库
所以,执行git add后,修改的快照进入了暂存区待commit,如果此时又修改了文件,那么修改还在工作区中,需要再次git add才能将新的修改放入暂存区。
配置:
git配置文件可以使用git config命令来读写:
1. /etc/gitconfig 作用于系统级 git config 加 --system选项时读写此文件
2. ~/.gitconfig 或 ~/.config/git/config 作用于当前用户 git config 加 --global选项时读写此文件
3. 当前仓库目录下的.git/config 作用于当前仓库目录对应的仓库 工作目录是该仓库,且git config 不加参数时,读写此文件
每一个级别覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量。
重要命令:
git config --list #列出当前配置
git config <key> # 查看某项配置,例如 git config core.editor
git config <key> <value> # 修改某项配置,例如: git config --global core.editor vim
远程仓库操作:
git clone -b master https://a.cn/x/y.git # 只克隆master分支(对于很大的仓库或网速很慢时挺有用的)
git remote -v # 查看远程分支, 加-v参数不但显示远程仓库短名字,还显示它的长名字。 origin是git clone给远程仓库默认生成的短名字,长名字就是那串长长的url:https://a.com/foo/bar
git remote show origin # 查看远程仓库origin的所有远程分支,这条命令可以查看本地分支跟踪的是哪个远程分支。(设置跟踪参考下面git push -u的说明)
git branch -r # 列出远程仓库的所有分支以及远程仓库的默认分支是哪个(例如origin/HEAD->origin/master)。 “git ls-remote [远程仓库短名字]” 也是类似功能的命令
git remote add <远程短名字> <url> # 关联远程仓库(远程仓库存在的情况下才执行这条命令)。一个本地仓库可以关联多个远程仓库
git fetch origin dev:Dev # 从远程仓库origin下载数据,指定远程仓库的dev下载到对应本地仓库Dev。语法: git fetch <远程短名> [远程分支][:本地分支] 直接执行git fetch会从默认追踪的远程分支下载
git pull # 是git fetch 和git merge的组合,参数指定方式参考上面的git fetch,我通常不适用git pull , 而是git fetch,然后再git merge。
git push origin LB:rb # 推送本地分支LB到远程仓库origin的rb分支。如果远程仓库origin没有rb分支,则会在远程origin下新建一个rb分支。如果直接执行git push那么就将本地分支推送到默认的远程仓库。如果本地仓库关联多个远程仓库,可以直接执行git push fooRep来指定将本地分支的变更推送到远程仓库fooRep下。如果推送的时候,提示没有对应的上游分支可以增加-u选项(--set-upstream选项的缩写)来建立对远程分支的跟踪,一旦使用-u来跟踪远程分支了,后面直接git push,不用跟其他参数了。这里我们执行git remote show origin就会看到本地的LB追踪了远程仓库origin的rb分支
git push --all origin # 不管本地是否存在对应的origin远程分支,都会将本地的所有分支都推送到远程主机.
git rename origin gitee_foo # 把远程仓库名origin重命名为gitee_foo
git remote rm gitee_foo # 删除远程仓库,也可以: git push origin --delete dev //假设远程仓库短名是origin
注意: 一般而言,dev、debug之类的分支是不需要推送到远程仓库的,一般是把这些分支合并到master分支后,推送master到远程仓库
分支操作:
git branch #查看分支 -v 或 -vv会显示详细信息
git branch --merged #查看所有已合并的分支, 同理 --nomerged查看所有未合并的分支
git branch <branchName> #创建分支
git branch <brName> <commitID> #以某个commitID来创建分支,非常有用
git checkout <branchName> #切换分支, 加-b参数标示创建并切换分支
git checkout origin/br0 #对于远程clone下来的仓库origin,切换到它的br0分支
git checkout -b <local-br> <remote>/<br> #创建一个与远程分支remote/br相对应的本地分支 #针对特定标签建分支
git checkout -b <brName> <tag> #针对特定标签建分支
git merge <branchName> #合并分支到当前分支。 通常合并分支时,git一般使用”Fast forward”模式,禁用FF模式,需要加参数--no-ff ( 非FF合并后的历史有分支,能看出来曾经做过合并,而FF看不出来)
git branch –d <branchName> #删除分支-D表示强制
git branch -u <remote>/<brName> # 设置已有的本地分支来跟踪一个刚刚拉取下来的远程分支。 -u的长选项格式是--set-upstream-to。 此时@{u}或@{upstream}是<remote>/<brName>的快捷方式。所以git merge @{u}相当于git merge <remote>/<brName>
git push origin --delete branch1 #删除远程分支, 也可以写成 git push origin :branch1
注意:master分支应该是非常稳定的,一般不在上面干活。在dev分支上干活,然后将稳定了的dev分支合并到master分支。团队协作是各个成员将自己分支不断合并到dev分支。
工作进度隐藏:
git stash save "stash_comment" #保存完成一半的工作, 此时git status提示是干净的。
git stash list #列出已经隐藏的git栈
git stash apply stash@{N} #恢复到stash N
git stash clear #清除缓存的stash
git stash pop #将stash从git栈中弹出
git drop stash@{N} #删除某个stash
git stash show stash@{N} #查看某个stash
比较差异:
git diff [<path>...] #使用默认对比工具来比较
git difftool [<path>...] #使用第三方工具比较差异, 例如BeyondCompare。关于将beyondCompare用于git,参考另一篇文章
git diff/difftool [file] #对比工作区和暂存区的差别(git add之前的)
git diff/difftool --staged [file] #对比暂存区和上次commit的差别
git diff/difftool <commit-id> <commit-id> #比较两个版本(用commit-id指定)的差异
git diff/difftool HEAD [file] #比较工作区和最后一次commit的差异
git diff/difftool master ReadMe.txt #比较当前分支和master分支的ReadMe.txt文件的差异
git log dev ^master # 比较dev分支有,而master分支没有的提交
git log master..dev # dev比master多了哪些提交? ..可以理解为from->to
git log master...dev # 不知道谁的提交多,只是想比较有哪些提交差异.加--left-right 参数可以显示提交所属的分支是哪个(例如git log --left-right master...dev 的<箭头表示master的)
git whatchanged <file> # 查看某个文件的变更的详细提交记录,简单记录可以使用git log --pretty=oneline <file>。 查看完后,可以git show <commit-id> [file]或者git difftool <commit-id1> <commit-id2> [file]来查看具体变更
git blame <file> # 显示文件的每一行最后变更的commit id
撤销修改 / 版本回退 :
git checkout -- ReadMe.txt proto.c #从暂存区中恢复文件到工作区。也就是执行add之前反悔了。这里的"--"表示选项的结束,如果没有任何选项,可以省略 "--"
git reset --soft <commit-id> # HEAD移动到 commit-id 指定的那个提交(不是针对单个文件或目录,所以参数中不能跟路径),也可以用HEAD~N的方式来指定重置到前N个版本。
git reset --hard <commit-id> #HEAD、暂存区和工作区都重置到commit-id指定的提交(不是针对单个文件或目录,所以参数不带路径)。这个有点危险,如果误操作,只能通过git reflog 查一下历史操作的commit-id才能恢复
git reset --mixed <commit-id> [path] #HEAD、暂存区都重置到commit-id指定的提交,工作区不变, --mixed是默认选项,参数可以带路径(可以针对单个文件或目录执行重置)
我测试的结果: --soft是只重置HEAD指针, 工作区和暂存区不变。 不能带路径。
--hard是HEAD、暂存区和工作区都重置(危险操作) 。不能带路径。
--mixed是重置HEAD和暂存区(默认参数)。 工作区不变。可以带路径,但是带路径会有warning
git revert <commit-id> #revert和reset的区别是,revert是将旧版本变成一个新的提交. 可以revert常规commit也可以revert分支合并的commit, 后者需要-m <1|2> 来指定保留哪个分支,1表示当前分支
例如下面revert合并的commit:
--A------B-----C------M----X----Y(revert)
-----a-----b-------/
如果当前处于上面分支X状态(此时上面分支有AaBbCX的提交), 如果Y处执行git revert -m 1 M的CommitID 则上面的分支只有ABCX的提交,(也就是丢弃了ab的提交)
如果Y处执行git revert -m 2 M的CommitID 则上面的分支只有AabX的提交(也就是丢弃了BC的提交). 注意这条命令是在Y处执行,而非上一条命令执行完后执行
revert注意的地方,可以到https://segmentfault.com/a/1190000012897697看例子
修改已完成的提交(commit后,可以多次修改该commit):
例如:
git add proto.c
git commit -m '修正proto.c的bug' #突然发现漏了proto.h和gen_id.c,忘了移除tmp.txt,于是执行:
git add proto.h gen_id.c
git rm tmp.txt
git commit --amend '修正proto.c和gen_id.c的bug' #此时git log --stat 只能看到修改后的提交
删除文件:
git rm <file> ; git commit -m 'xxxx' #删除工作区和暂存区文件,并且提交
git rm --cached <file> ; git commit -m 'xxx' #删除暂存区文件,并提交。此时工作区文件还在,这是版本库不再跟踪它了。
查看提交历史:
git log -3 #查看最近三次提交
git log --after/before='1999-09-09' #查看1999-09-09前/后的提交。 指定时间可以使用类似"1year"、“8days”之类的。 --after等同--since,--before等同于--until
git log --author/--committer=<name> #按作者/提交者查看,注意作者和提交者的区别
git log --grep=<pattern> #按正则搜索注释
git log hello.c # 查看修改了hello.c的commit,查看针对某个路径的commit,如果有很多选项时,可以使用 -- 标示选项结束: liru
git log --graph #形象显示commit log
git log --stat #显示commit 历史涉及的文件
git show --stat [commitID] #显示某个commit涉及的文件变更
git log --pretty=format:%h" "%an" "%s #显示短hash、作者、注释(空格分隔)。更多%号格式参考git lot help
git log --pretty=oneline #单行显示commit历史
git log -- server.c # 查看 server.c 的提交记录
标签:
git tag # 查看已有标签
git tag -l 'v1.*' # -l是小写的L,只列出 v1.* 的版本号, 批配处v1.0 v1.1 v1.2...
git tag v1.0 # 打一个轻量级标签, 轻量级标签是对某个提交的引用。不指明commit-id的情况下,默认引用HEAD
git tag -a v2.0 -m '这是v2.0附注标签的说明' # 打上一个附注标签,-a是的a是annoted的首字母。附注标签是一个仓库中的独立对象。它有自身的校验和信息,以及标签说明。
git tag -s v2.1 -m '这是v2.0附注标签的说明' # 打上一个GPG签名的附注标签。 -s是signed的首字母. 使用git show v2.1会看到GPG签名
git show v2.0 # 查看标签v2.0的详情
git tag -v v2.1 # 验证含有GPG签名的标签(keyring中需要有GPG公钥)
git tag v1.2 df1796d00a50a5567fac0b7d689402942943e01d # 给df1796d00a50a5567fac0b7d689402942943e01d这个提交打轻量标签
git tag -a v2.3 -m '忘记打标签了' # 给df1796d00a50a5567fac0b7d689402942943e01d这个提交打附注标签
git push origin v2.3 # 将标签推送到远程仓库origin。(默认情况下git push是不会推送标签的,所以需要专门推送标签,标签才能体现在远程仓库上)
git push origin --tags # 将所有标签推送到远程仓库
------------------------------------ 注意力分割线 --------------------------------------
我做的小测试:
mkdir x; cd x
git init
git remote add ts2 https://gitee.com/xxx/test2
git fetch ts2 # 抓取的副本在本地的工作空间是看不到文件的。ps: 可以git fetch ts2 dev 只fetch dev分支
git checkout -b dev ts2/dev #此时就可以看到ts2/dev对应的的本地dev分支了。此时执行 ls -l 就能看到工作空间有文件了
上面的最后两步可以这么玩:
git fetch ts2 dev:dev #fetch ts2的dev分支,并在本地创建dev分支
git checkout dev #切换到本地dev分支,此时就能看到远程的ts2/dev分支在本地对应dev分支的工作空间文件了
git checkout -b dev2 #建立本地dev2分支
git push -u ts2 dev2 #把本地分支dev2 推送到远程分支ts2上,结果在gitee服务器上看到了dev2分支