Git的工作原理:
Git 常用命令集
如何修改本地仓库地址:
1.打开git bash
2.cd
你想创建仓库的位置例如 cd F:\mygit_repo (名字中间有空格的如Program file写为Program” ”file)
3.使用 git init
命令
如何修改远程仓库地址:(待补充)
右键菜单中, Git Init Here
直接在当前目录下创建一个代码仓库, Git Gui
打开Gui的图形操作页面
配置属性:
git config --global user.name “balabala”
配置用户名
git config --global user.email “balabala@aa.com”
配置邮箱
git config --global user.name
后面没接东西,显示用户名
用户自己的全局配置保存在~/.gitconfig
中,参考链接
生成SSH Key: ssh-keygen -t rsa -C "youremail@xmail.com"
,会得到id_rsa和id_rsa.pub,一般是保存在家目录的.ssh目录下。复制id_rsa.pub中的内容到github的settings→SSH and GPG keys里,就完成了密钥的配置.
密钥里面包含了用户名和密码的信息,所以不同设备下的同名用户可以使用同一个密钥,同一个设备下的不同用户不能使用同一个密钥。
Git使用本地仓库之基本操作
创建本地仓库:cd到仓库的目录下,然后输入 git init
,即可创建一个本地代码仓库。我们想删除代码仓库只需使用rm -rf 仓库名
命令,把这个文件夹删掉即可。
将文件添加到本地暂存区(原理图中的index):add命令
git add 文件名
不需双引号,文件需已存在,文件名应带上后缀
git add -u
一次性添加所有被删除和修改的文件到暂存区,方便快捷
git add -A
一次性添加全部文件,包括untracked文件
git add -p filename
对指定文件的部分修改:有时候一口气写了很多东西,但是这些东西应当分开提交,用这个命令进行交互式处理,选择需要的部分进行提交,详见参考链接。其实 VSCode自带了这个功能,只是有时候它显示不出来,就只能手动敲git命令来完成了。
git ls-files
列出已被add的文件(已被commit进的也算)
将某个文件剔除出ls-files列表,但不删除该文件本身:
git rm --cached xx.py
git -C path command
可以在指定的目录path
下运行git
命令,相当于cd path && git command
,做自动化脚本的时候比较方便
添加与提交修改
提交修改:commit命令,将已add的东西全部commit进本地代码库
基本格式:git commit
提交时可以给本次提交加上一些声明:git commit -m “声明内容”
声明内容可以在以后的某一天想看的时候用git log
命令调出来看,git log
返回的内容包括版本号(那个长长的字符串),提交人的姓名和邮箱,提交时间,修改的内容,也即声明内容
git commit --amend
可以修改最近一次commit的描述,参考资料,其他参考资料
做了一段时间工作的时候,想查看我们做了些什么改动:git diff
命令,查看现有文件与暂存区中文件的差别(而不是与commit了的文件做比较),命令返回的文本中,红色-对应的是被删除了部分,绿色+对应的是新添加的部分。
git diff branch1 branch2 --stat
//显示出所有有差异的文件列表
git diff branch1 branch2 文件名(带路径)
//显示指定文件的详细差异
git diff branch1 branch2
//显示出所有有差异的文件的详细差异
撤销(文档写错一不小心按了Ctrl+S):
文档尚未add的情况:可以先用git diff看一下改了哪些东西,可以手动回去改回来,也可以直接git checkout 文件名,就能一键把文档恢复成前一个add的版本啦!
如果一不小心add了:checkout无效,要先取消添加才能撤回提交:
git reset HEAD 文件名
git checkout 文件名
再一不小心commit了:只能回退版本了:
git reset --hard HEAD
--hard
参数表示放弃所有的改动并恢复文件至某个版本
git reset详解
在Git中,用HEAD
代表当前版本,上一个版本就是HEAD^
,再上一个版本就是HEAD^^
,依此类推。
HEAD
也可以替换成版本号,版本号通过git log
获取。实际使用时,一般版本号只输入前7位就不会出现重复的情况了,可以不输入那么多位。
回退后,你突然后悔了,想回退回新的那个版本,可是遗憾的是,你键入git log却发现没有了最新的那个版本号:还有办法,git reflog命令可以看到之前输入的每一条指令,以及执行指令时对应的版本号(前7位)
还原某个特定的文件到之前的版本:git checkout 版本号 文件名
git checkout master
回到主分支上
git revert
用于逆转之前的提交,比如A-B,现在想要在A的基础上改,但是B里面又有一些有用的东西,不能git reset
,这时可以考虑用git revert HEAD
,它会追加一个commit,将代码还原到A的状态,git revert HEAD~2
就是还原到A的上一个提交的状态,而逆转之前排在前面的B等依然存留在commit中,不会被移除。如果是要把一个很久之前或者由于某种原因曾经出现在commit tree但现在不在的提交还原出来,用revert
就很费劲,它好像一次只能回退一步,总之没有办法一步到位,对于这种需求还是自己git diff
然后手动更改代码然后提交好一点,或者是git checkout
回去把整个文件的内容拷回来。参考链接
新命令switch和restore
有一些目录或者文件我们不想添加到仓库中,比如lib,gen,bin以及我们自己的data目录等等,我们可以在代码仓库的根目录下创建一个名为.gitignore
的文件,然后把要忽略的文件名或文件格式写上去就可以了!.gitignore详解
有时候刚提交完一个版本,回头就发现代码有问题,这个版本是个不可用的版本,我们想要把它删掉,要怎么做呢:要使用rebase命令
git rebase -i
命令除了可以删掉版本,也可以合并两个版本,比如合并commit中的内容,其他功能还没有探索过
其实rebase
原本的用法是在合并分支上,跟merge
类似都能合并两个不同分支的修改,但具体行为不同,详见参考链接1,参考链接2
获取git log
的缩略信息,保存在同步日志文件中(参考链接1,参考链接2,参考链接3):
# commit id,branch和log信息,-1表示最近的1次提交,也就是刚拉下来的提交,
# --oneline表示把所有信息包括原本多行的日志都折到一行里显示
git log -1 --oneline
# author信息,sed -n获取某些行,2p表示获取第2行
git log -1 | sed -n '2p'
分支
创建分支:git branch 分支名
分支间的切换:git checkout 分支名
,切换后文件的内容即被改变
git checkout -b dev
创建分支dev并切换到dev,此语句相当于 git branch dev
加git checkout dev
查看所有分支:git branch
当前分支的前面有*标示
合并分支:git merge 分支名
将某一分支A合并到当前分支上,实际上是当前分支点移到了A点(注:如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起)。
出现冲突:两个分支由于工作内容不同,或者是两个开发者后来改了什么东西,文件内容出现冲突是很正常的事情。在出现冲突时,冲突的文件将会被写入类似下面的信息:
该信息指出,当前分支(HEAD)在与dev-object-jw2005的分支合并的过程中出现了冲突,箭头往左表示冲突开始,也即“<<<HEAD”上面的部分没有冲突;箭头往右表示冲突结束,也即“>>>dev…”下面的部分没有冲突;中间就是冲突的部分了,从<<到====的部分是上分支的内容,从>>到====的部分是下分支的内容。可以看到,这次冲突中,HEAD的内容为空(或者说是两个空行),dev分支则是多了四行语句,那这次只需要将四行代码放进来,即可解决这个冲突。
删除分支:git branch -d 分支名
删除本地分支后需要同步到远程,删除远程的分支:git push origin --delete 分支名
,参考链接
分支合并时出现merge conflict等可以看这个:Git-分支-分支的新建与合并
远程仓库的搭建与同步
新建立一个远程仓库之后,首先要把本地与这个仓库联系起来:
git remote add origin address
, address
指远程仓库的ssh地址,这样就把该仓库与我们本地的origin
标号联系了起来,可以认为origin
就是那个远程仓库的引用,以后push和pull就不需要输入一长串的ssh地址了,用origin
替代就可以了。
git remote -v
命令显示当前已添加的远程仓库符号以及对应的地址
本地仓库与远程仓库同步:
从远程仓库下载:git clone url_repository aaa
,注意到clone下来的东西是在当前目录下新建了一个文件夹,名字为aaa(一般不需要加aaa,此时文件夹名称将为github项目的名称)。新版本Git中clone
只会下载一个分支,其他分支要自己手动下载:git fetch origin dev
(dev为远程仓库的分支名)。git clone -b 远程仓库分支名
或 git clone commit版本号 [本地文件夹名]
,相当于clone
下来之后又执行了一次checkout
到我们所指定的分支或版本。clone
是指本地仓库没有这个东西时,“无中生有”,复制下来一套完整的文件。之后就不再进行clone
了(也无法再进行clone
了,不能clone
到非空的目录),后面要用pull
来更新文件的版本。
从远程仓库同步到本地:git pull origin master
从本地仓库同步到远程仓库:git push origin master
从远程仓库clone
下来之后,git branch -a
会看到remote/
开头的分支,对应于远程仓库上的分支。我们可以checkout
到那上面,它只是远程仓库在本地的一个副本,修改它并不会修改远程仓库上的内容。
当存在多个分支的时候,我们就需要指定从哪个分支同步到哪个分支了:
git push origin 本地分支名:远程分支名
,不需要两个名字相同,想推到哪推到哪
pull也是一样,git pull origin 远程分支名:本地分支名
,需要注意的是如果此前本地没有这个分支,那么第一次拉取远程分支应该使用git checkout -b 本地分支名 origin/远程分支名
(参考链接),否则可能会出现与当前分支的merge conflict,原因尚且未知。
分离(detached)头:由于远程分支毕竟不是本地的分支,逻辑上我们只能修改本地的分支,因此如果checkout
到一个远程分支上,这个HEAD将被认为是“分离的”,它可以进行跟普通HEAD同样的操作,也能push和pull,但只要checkout到了别的分支,之前在分离头上所做的工作就会全部丢失。那如果一不小心,没有先git stash
,就在分离头上把做了一段时间的工作给提交了该怎么办呢?只需在当前分离头位置建立一个分支:git branch tmp
,该操作会得到一个正常的分支,内容与分离头一致;然后切回原本的分支:git checkout master
,然后汇合:git merge tmp
,此时工作就汇入原来的分支啦!最后把临时分支删除:git branch -d tmp
,大家就当无事发生过~(参考链接)
fetch
的功能比pull
稍弱,只更新远程仓库在本地上的副本,不对本地分支的内容进行更改,而pull则是把本地分支的内容也做了更新。pull
相当于fetch
+merge
有时候远程仓库有小更新,而本地分支已经更新了很多个提交,此时两个仓库差别比较大,直接merge
可能不太好,这时可以考虑先fetch
下来,然后一个一个文件检查:git checkout origin/main xx.py
,该命令会用远程分支中的文件覆盖当前的文件并add
,我们只需要取消add
,就能在VSCode
里看到文件的变化,然后修改,改完再自己commit
一次,最后再merge
的时候出现的conflict
或者modify
就会少掉很多,不容易漏掉啥被覆盖了都不知道的文件。参考链接
给远程仓库建立一个新分支:先在本地git branch dev
生成一个新分支,把东西commit进这个分支的仓库之后,再git push origin dev
就可以了。
在进行pull和push的时候需要指定本地分支与远程分支的映射关系,否则会报no tracking information错误或者has no upstream branch错误,详情请看:https://blog.youkuaiyun.com/tterminator/article/details/78108550
git branch -u aaa
将当前本地分支与远程分支aaa建立映射关系(此时无需指定URL)
git branch --unset-upstream
解除当前本地分支与远程分支的映射关系
Git之使用GitHub搭建远程仓库
git checkout -b 本地分支名 origin/远程分支名
从远程拉取一个本地没有的分支
标签
commit id可读性比较差,我们可以通过给某个commit打标签的方式,来快捷回到之前的某一个提交,参考链接
添加标签:
# 添加本地tag
git tag -a <tag_name> -m "comment"
# 推送到远程
git push --tags
删除标签:
# 删除本地tag
git tag -d <tag_name>
# 推送到远程
git push origin :refs/tags/<tag_name>
与他人一起在github上做项目:如何加入别人的Git项目——Git Fork指南
进阶: 在自己的服务器上搭建一个git仓库, 起到github的功能
本地上的.git/
目录我们称为项目仓库.
git init --bare aa.git
生成一个./aa.git/
目录. 如果直接git init aa.git
, 则会生成./aa.git/.git
目录, 不满足我们的要求.
后续操作详见: 如何在服务器上搭建Git版本仓库(干货)
gitea可以用来自建git服务器:安装方法
进阶:清理git仓库中无用的大文件
参考链接,好文
该说的都在这篇博客里头了,没啥要说的了,哈哈。
我自己使用的时候,clone本地仓库之后.git仓库依旧没见小,但push上一个全新的空仓库时明显变小了,但是push -f到原来的仓库反而从1.0M变成了1.2M,很奇怪,最后采用新建仓库再推的方式,在保留commit log的情况下把1.0M删减到300+K。
上面的博客需要git push -f,这篇也是,但是下面这篇介绍的方法不需要git push -f,我自己还没有试过,仅做收录:BFG Repo-Cleaner
在Git仓库中添加其他Git仓库
git submodule add <url> <path>
其中,url为子模块的路径,path为该子模块存储的目录路径,参考链接
git submodule的使用
git submodule
查看子模块信息:
352d9xxx image_capture (heads/master)
+d486cxxx socket_python (heads/master)
编号表示父模块当前使用的子模块版本,这个版本与我们cd
到子模块目录下git log
最上一条版本id是一致的。要更改使用的子模块版本,就跟无submodule情形下一样,用checkout
就可以了。
如果编号前面有个+
,表示父项目使用的子模块版本相比之前发生了变动;如果编号前面有个-
,说明子模块的代码还没有拉下来。
把子模块的代码拉下来:先git submodule init
,再git submodule update
就可以了,参考链接
git submodule 相关操作解析
修改子模块之后只对子模块的版本库产生影响,对父项目的版本库不会产生任何影响,所以子模块可以安心开发提交做啥都行,只要父项目没有更改使用的子模块commit id,对于父项目而言子模块就是一直没有变化的,这就很好地隔离了上下层模块,个人觉得非常好用。如果父项目需要用到最新的子模块代码,我们需要更新父项目中submodule commit id,默认的我们使用git status就可以看到父项目中submodule commit id已经改变了,我们只需要再次提交就可以了。
在子模块目录下git pull
跟在父级目录git submodule update
是等价的,都是把子模块更新到最新版本。如果子模块里还有子模块,这个操作只会更新子模块,因此还需要手动在子模块目录下git submodule update
才能更新子模块的子模块。在子模块太多的时候,一个一个手动更新令人抓狂,因此我们要用更便捷的方法来完成这件事情:git submodule update --remote 子模块名
,不写子模块名则会更新所有子模块,将它们更新到远程分支对应的版本(注意:如果远程分支更旧,也依然会被checkout
到远程分支的版本,也就是版本回退,此时子模块会位于detach head上,但输出信息没有什么不同,所以一不留神就挖了个坑不知道什么时候会跳进去,要多留意)。这里说的远程分支默认是master
,如果想要使用别的分支,则需要手动设定一下:git config -f .gitmodules submodule.子模块名.branch 分支名
。此时如果子模块还有子模块,它还是不会更新,需要增加一个--recursive
参数。所以最后的完整子模块更新命令应该是:git submodule update --remote --recursive
。参考链接
(后记:上面的命令都有各种不适合我的问题,最后选择了git submodule foreach git pull origin --recurse-submodules
)
foreach
这个命令挺好用的,比如git submodule foreach git branch
可以直接查看所有子模块的分支,避免误操作导致在detach head上提交还浑然不知。
通常我们提交带子模块的仓库时,都是有满足父模块和子模块的版本对应关系的,在回退版本的时候,单纯git checkout 版本id
只会回退父模块,而不会回退子模块,这使得版本不再匹配。使用git checkout --recurse-submodules 版本id
来让子模块也同时checkout
回与当时的父模块版本相匹配的版本。git reset --hard
也是类似的道理,需要加上--recurse-submodules
。
删除子模块(参考链接)
git submodule deinit {MOD_NAME}
# 删除.gitmodules中记录的模块信息(--cached选项清除.git/modules中的缓存)
git rm --cached {MOD_NAME}
修改子模块的url并更新到远程(参考链接):
.git/config
文件直接管控url等信息,但这个文件是支持用户自定义的,它并不参与版本控制,因此我们对这个文件的更改,是没有办法推送到远程仓库的。
参与版本控制的子模块相关的配置存放在.gitmodules
分支,对这个文件的修改可以推送到远程仓库;但修改这个文件并不会改变当前git的配置,因此需要以某种方式让这个更改应用到.git/config
中。
# 修改子模块的url
vim .gitmodules
修改 .gitmodules 文件中对应模块的url属性;
使用 git submodule sync 命令,将新的URL更新到文件.git/config;
再使用命令初始化子模块:git submodule init
最后使用命令更新子模块:git submodule update
有时候在手工核对子模块的时候,文件里头只有一个commit id,没有分支,找不到这个提交,此时可以通过git branch -r --contains commit_id
找到包含这个id的所有分支(参考链接)。
多人合作时带子模块的仓库有时候会被人无意中错改了子模块的版本,为了找出子模块的变化历程,需要使用这条命令:git log --patch master -- path/to/submodule
,参考链接
Git的妙用
git grep someword folder_name
在某个目录下的所有文件内容中搜索某个关键字,-n
可以返回所在行数(VsCode的Ctrl+Shift+F搜索包含了这个功能)
git stash
可以保存当前的工作状态。在debug的时候,如果写好一段代码,测试完之后想做比较大的修改,但当前的代码又没有完整到可以单独作为一个版本提交,在之前我是branch之后在debug分支提交一份,后面如果要回到大改前的版本可以用git reset --hard
。而现在有了git stash
,我有了另外一种更方便的选择:在第一次修改结束后,运行git stash push
(或者直接git stash
,二者等价),当前基于上一个版本有改动的部分会被保存起来,同时将这部分改动抹去,当前代码恢复到HEAD
版本。恢复到上一个的版本的好处之一是这时我们可以checkout
了,不会被提示有未保存的改动。保存之后,git stash list
可以看到我们目前的存档点的概览,git stash diff
可以查看保存的改动具体是什么,git stash apply
可以把当前代码恢复到存档点。如果有多个存档,具体恢复哪一个存档通过git stash apply stash@{n}
中的n来决定,n=0就是最近一个存档点,n=1的倒数第二近,以此类推,跟HEAD~2是一样的道理。git stash pop
会在回档之后顺手把存档点给删掉,而apply
则不会,apply
可以搭配git stash drop
手动删档。git stash clear
是删除所有存档。create
跟restore
应该是用不到了,略过。
git stash save "xx" -u
把untracked的文件也一并推进堆栈。
git stash list --date=local
显示stash的时间点,方便我们回溯。
参考文档
写到这里突然想到,其实还可以直接commit第一个版本的代码,在大改结束之后commit一个稳定的版本,然后git rebase HEAD~2
,也同样可以实现我们的要求。
git blame
可以查看单个文件的修改历史,可以清晰地看到一个文件在n多个版本里面是什么时候做了改动,例如我发现版本A跟版本B的xx.py有一个地方不一样,但是他们中间隔着几十个版本,我不想一个一个去翻他们的git diff,那用blame就能只看这一个文件在那个不一样的地方,是在哪个版本被修改过的,非常舒服。gitea里有这个功能,github不知道有没有。(VsCode安装gitlen插件后可以看blame,而且在file history里可以看得更清楚)
git blame用法
有时候我们的提交里头有很多黑历史,不想给别人看,只想把最好的一面展现出来,把最新的几个提交放到远程仓库,这时候可以曲线救国:首先git checkout
到要放远程仓库的提交的最早一个提交,把代码拷到新目录A,删掉.git/
,重新git init
, git add
, git commit
,然后主仓库checkout
到下一个提交,整个目录拷到新目录B,用A的.git/
覆盖掉B的.git/
,然后在B里头git add
, git commit
,接着主仓库checkout
到下一个提交,整个目录拷到新目录A,用B的.git/
覆盖掉A的.git/
,然后在A里头git add
, git commit
,如此往复,直到更新完毕。
提升使用体验
git操作免密码
gitea目前由于某种原因,我一直没有办法用上ssh-key,只能http,http每次都要输入账号密码,很烦人,有没有什么办法让它自己搞定?
方法一:修改.git/config
里头的url:
# 这里仅节选需要修改的键值
[remote "origin"]
# 原始url
# url = http://120.24.55.96:3000/repository_path
# 添加了账号密码的url
url = http://username:password@120.24.55.96:3000/repository_path
fetch = +refs/heads/*:refs/remotes/origin/*
直接把账号密码写进来,一劳永逸。缺点是每个仓库都要这么搞一遍,如果仓库比较多,也还是很烦人,而且密码不能带@
方法二:git config --global credential.helper store
,启动credential helper的记录模式,只需输入一次账号密码,后续就都是自动输入了,就像chrome的自动填充一样,方便快捷,参考链接
与方法一相比的局限性是,如果是在dockerfile里对仓库进行处理,方法一可以配置好账号密码,方法二中间需要人工干预输入账号密码。
git config --global --unset credential.helper ## 取消自动记住密码
git config --global credential.helper 'cache --timeout=600' ## 缓存10分钟
不同操作系统间的换行符冲突
Windows是\r\n
,Linux是\n
,两者混合提交会导致git频繁对换行符的变更对大量文件做变更提交,这显然是我们不愿看到的,因此我们应当做一些设置(参考链接):
# 禁止提交混合\n和\r\n的文件
git config --global core.safecrlf true
# 自动把\r\n换为\n
git config --global core.autocrlf input
同时要在VSCode里头把files:eol
的值设为\n
(参考链接)。
其他
疑难解答
- Q:
git pull
时出现错误refusing to merge unrelated histories,无法pull
A:这是因为远程仓库origin上的分支master和本地分支master被Git认为是不同的仓库,所以不能直接合并。要合并两个不同的项目,git需要添加一句代码,在git pull,这句代码是在git 2.9.2版本发生的,最新的版本需要添加–allow-unrelated-histories。假如我们的源是origin,分支是master,那么我们 需要这样写git pull origin master --allow-unrelated-histories需要知道,我们的源可以是本地的路径 - Q:
git clone
时报错server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none
A:解决方案 - Q:
git diff
中文乱码
A:export LESSCHARSET=utf-8
,参考链接 - Q:
git pull
时出现Failed to connect to 127.0.0.1 port 7890: Connection refused报错
A: 不知道什么时候被加了代理,git config --global http.proxy && git config --global https.proxy
看看是不是真有代理,如果有就git config --global --unset http.proxy && git config --global --unset https.proxy
取消掉,参考链接 - Q:
git clone
没问题,要push
回去的时候报错"fatal: unable to access ‘xxx’: The requested URL returned error: 403"
A: 可能是修改过Gitea的密码,本地还用的旧的,就通不过,
git config --local --unset credential.helper && git config --global --unset credential.helper && git config --system --unset credential.helper
清除记住的账号密码,重新push,提示输入账号密码,问题解决(参考链接)。