# 输入命令生成密钥对,替换成自己邮箱,然后一路回车
ssh-keygen -t rsa -C "[email protected]"
# 将生成的公钥打印出来复制,将这串文本复制粘贴到GitHub的Setting->SSH and GPG keys中
cat ~/.ssh/id_rsa.pub
# 输入命令检查是否绑定成功,输入yes后,如果出现Hi,xxx!则绑定成功
ssh -T [email protected]
#如果配置以后报错:Permission denied (publickey)
#则在 ~/.ssh/config 文件中加入一下内容:
#Host
#KexAlgorithms +diffie-hellman-group1-sha1
PubkeyAcceptedKeyTypes +ssh-rsa
Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量,配置方式有以下三种:
# 查看配置信息,一开始为空
git config --list
# 修改 /etc/gitconfig 文件,对所有用户的所有代码库都生效,很少配置这个
# 修改 ~/.gitconfig 文件,对所有代码库生效,一般都配置这个
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"
# 修改 .git/config 文件,只对当前的代码库有效
git config --local user.name "你的名字"
git config --local user.email "你的邮箱"
# 配置后,远程仓库提交的commit里对应的用户即为 user.name
# 查询git版本号
git version
# 下载版本号对应的git-completion.bash文件,如2.40.0就是这样
wget https://raw.githubusercontent.com/git/git/v2.40.0/contrib/completion/git-completion.bash
# 最好是将其作为隐藏文件放在~/路径下
mv git-completion.bash ~/.git-complition.bash
# 在.bash_profile文件末尾加上,其中.代表source命令
if [ -f ~/.git-completion.bash ]; then
. ~/.git-completion.bash
fi
# 执行~/.bash_profile文件中的命令,如果不起作用,就把.bash_profile中的~换成绝对路径
source ~/.bash_profile
一份本地代码,可能会用到两个或者更多的远程仓库地址。比如有一个上游社区的项目,仓库在gitlab上,我在我自己的帐号上fork
了一份,并将其clone
到了本地。如果上游项目更新过,fork
的项目不会同步的,我需要在本地pull
上游社区的内容,此时就需要我将社区的仓库地址add
到本地。
# 可参考“5.1 提交代码至GitHup”
git init #在目录中创建新的 Git 仓库,ls .git/可查看仓库信息,所有有关项目的快照数据都存放在其中。
git remote add [shortname] [url] #添加远程版本库的地址,shortname为远程地址的别名
git remote rename oldname newname #修改现有远程地址的别名
git remote show [shortname] #显示别名对应仓库的信息
git remote rm name #根据别名,删除现有远程仓库
git clone [url] #拷贝一个 Git 仓库到本地,让自己能够查看该项目,或者进行修改。
git clone --branch br_name --depth 1 [url] #只拉取指定分支到本地仓库
git add [file1] [file2] ... #将工作区文件或目录提交到暂存区。
# 只提交一个文件中的部分修改代码:
# git 中用"hunk"来表示一个文件中邻近区域中的代码修改块,
# 比如用git diff 查看修改时,两个@@符号分割的一个区域就是一个hunk。
git add [-p|--patch] [file1]
# 执行该命令就会进入交互式的操作界面:
y - 将当前的hunk进行提交
n - 不提交当前hunk,进入下一个hunk
q - 退出交互式界面,不提交当前hunk以及后面的所有hunk
a - 提交当前hunk以及后面的所有hunk,并退出界面
d - 不提交当前hunk以及后面的所有hunk,效果与q一样,也会退出交互式界面
g - 选择一个hunk并跳转过去,输入后会列出所有hunk的编号,输入编号就跳转到对应的hunk
/ - 输入一个正则表达式,选择一个包含搜索词的hunk进行跳转
j - 暂时不确定是否保存当前hunk,跳转到下一个没确定的hunk
J - 暂时不确定是否保存当前hunk,跳转到下一个hunk
k - 暂时不确定是否保存当前hunk,跳转到上一个没确定的hunk
K - 暂时不确定是否保存当前hunk,跳转到上一个hunk
e - 手动修改hunk块的内容,将`-` 开头的行替换为 ` `则不会删去这行,删除`+`为首的行则不提交这个新增,以`#`开始的行会被忽略
? - 显示帮助信息
git commit [file1] [file2] ... -m [message] #给暂存区域生成快照并提交至本地仓库,[message] 是一些备注信息。
git commit -a #-a参数设置修改文件后不需要执行 git add 命令,直接提交至本地仓库。
git commit --amend [file1] ... #使用一次新的commit,替代上一次commit,提交至远程仓库也可以修改。
# 从将本地的分支版本上传到远程并合并
# PS:分支推送顺序的写法是<来源地>:<目的地>,所以git pull是<远程分支>:<本地分支>,git push是<本地分支>:<远程分支>
git push <远程主机名> <本地分支名>:<远程分支名> [--force]
git push origin HEAD:refs/for/br_name #将当前分支的最新以此commit推送到远程主机origin上的对应的br_name分支上。
--force #强制推送,会使用本地分支的commit覆盖远端push分支的commit。
#即,如果其他人在相同的分支push了新的commit,这一举动将“删除”他的那些commit!
#就算在强制push之前先fetch并且merge或rebase也是不安全的,
#因为这些操作到push之间依然存在时间差,别人的commit可能发生在这个时间差之内。
#如果push的commit不是首次push,也就是之前push后又修改了一些东西再push,一般用该参数
--force-with-lease #强制推送,但将解决force存在的安全问题,如果远端有其他人推送了新的提交,那么推送将被拒绝。
#被拒绝时,需要fetch 仓库,然后确认其他人是否对此分支有新的修改,没有则可继续推送。
更新代码时可能会有冲突,可以使用git stash
命令将本地工作区备分,放入git栈中,工作区内容恢复到仓库head最后一次提交的内容。然后git pull / git fetch + git merge
更新或合并代码,最后再git stash pop
命令恢复备份到工作区,如过有冲突,需要解决冲突再git stash pop
。
# PS:注意,分支推送顺序的写法是<来源地>:<目的地>,所以git pull是<远程分支>:<本地分支>,而git push是<本地分支>:<远程分支>
git pull <远程主机名> <远程分支名>:<本地分支名> #更新远程仓库中的代码到本地工作区间
git pull origin master #将远程主机 origin 的 master 分支拉取过来,与本地当前分支合并
git pull origin master:brantest #将远程主机 origin 的 master 分支拉取过来,与本地的 brantest 分支合并
git pull --rebase origin master:brantest
#当有多个人在master分支上开发的时候,当别人push新的commit上去,而你本地的commit还没有push
#在你push之前要先pull代码,此时就要加参数--rebase,从而实现变基操作,效果如下图
---E---> other
| | push
A---B---E---> master
|
---C---D--->brantest
# git pull --rebase master
---E--->other
| | push
A---B---E---> master
|
---C---D
git pull = git fetch + git merge
git fetch <远程主机名> <分支名> #将远程主机的最新内容拉到本地仓库
git merge [branch] #合并指定分支到当前分支
# eg:
# 从远程仓库更新代码,到本地仓库
[loongson@bogon luajit]$ git fetch origin larch64
来自 ssh://rd.loongson.cn:29418/luajit
* branch larch64 -> FETCH_HEAD
[loongson@bogon luajit]$ git merge FETCH_HEAD #FETCH_HEAD是git自己创建的临时分支
更新 bbb6188..644fafd
Fast-forward
src/lib_jit.c | 2 +-
src/lj_asm_loongarch64.h | 6 +++---
src/lj_jit.h | 6 +++---
src/vm_loongarch64.dasc | 5 +++++
4 files changed, 12 insertions(+), 7 deletions(-)
# 或者使用以下方式:
git fetch origin larch64:temp #temp是本地在创建一个临时分支
git diff temp #查看从远程拉下来的分支和当前所在分支的异同,如果有冲突要修改
git merge temp #合并temp分支到当前分支
git branch -d temp #删除临时分支
checkout
命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。
git checkout -b 本地分支名 origin/远程分支名 #将远程git仓库里的指定分支拉取到本地(本地不存在的分支)
git checkout -- #暂存区 -> 工作区间,工作区间修改,单位git add
reset
命令把当前 HEAD 和 branch 指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。
git reset [--soft | --hard | --mixed] [HEAD]
--hard #重置位置的同时,直接将working Tree工作目录、index暂存区及repository都重置成目标Reset节点的內容
#所以效果看起来等同于清空暂存区和工作区
--soft #重置位置的同时,保留working Tree工作目录和index暂存区的内容,只让repository中的内容和reset目标节点保持一致
#因此原节点和reset节点之间的【差异变更集】会放入index暂存区中(Stagedfiles)
#所以效果看起来就是工作目录的内容不变,暂存区原有的内容也不变,只是原节点和Reset节点之间的所有差异都会放到暂存区中
--mixed(默认)
#重置位置的同时,只保留Working Tree工作目录的內容,但会将Index暂存区和Repository中的內容更改和reset目标节点一致
#因此原节点和Reset节点之间的【差异变更集】会放入Working Tree工作目录中
#所以效果看起来就是原节点和Reset节点之间的所有差异都会放到工作目录中
HEAD #最新提交
HEAD^ #上一个提交版本
HEAD^^ #上上一个提交版本
HEAD~3 #上上上一个提交版本,往上3个版本
# eg:
git reset HEAD file #本地仓库 -> 暂存区,工作区中修改了文件且git add到暂存区,但是尚未commit
git reset --hard HEAD^ #回退工作区中的文件到上一个commit版本,也就是HEAD指针指向上一次commit
git reset --hard commit_id #回退工作区的文件到指定commit_id,也就是HEAD指针指向commit_id
git clean
从你的工作目录中删除所有没有 tracked,没有被管理过的文件。
#---git rm---#
git rm [-r] #将文件从暂存区和工作区中删除,-r递归删除
git rm -f #如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
git rm --cached #只从暂存区删除,保留工作区
#---------------#
#---git clean---#
git clean -n #是一次 clean 的演习, 告诉你哪些文件会被删除,不会真的删除
git clean -f #删除指定路径下的没有被 track 过的文件
git clean -f #删除当前目录下所有没有 track 过的文件
git clean -df #删除当前目录下没有被 track 过的文件和文件夹,-d递归删除
#不会删除 .gitignore 文件里面指定的文件夹和文件, 不管这些文件有没有被 track 过
git clean -xf #删除当前目录下所有没有 track 过的文件
#不管是否是 .gitignore 文件里面指定的文件夹和文件
#---------------#
n :显示将要被删除的文件
d :递归删除
f :强制运行
x :也删除忽略的文件
X :只删除忽略的文件
不熟悉查看历史提交记录可阅读Git 查看提交历史。
git log [] [] #查看历史提交记录。
--stat #显示commit历史,以及每次commit发生变更的文件
--pretty=oneline #选项来查看历史记录的简洁的版本
-5 --pretty --oneline #显示过去5次的提交记录
--graph #命令可以看到分支合并图
--author= #查找指定坐着的提交记录
-S [keyword] #根据关键字搜索提交历史
#查看历史提交的分支合并图,合并是加上--no-ff参数
git log --graph --pretty=oneline
# * 98a2387 (HEAD -> master) merge with no-ff
# |\
# | * be0b788 (dev) add merge
# |/
# * 598ea11 & simple
# * 0df82bb branch test
# * 85f02d0 add test.txt
git blame #以列表形式查看指定文件每一行代码的历史修改记录。
git reflog #记录了每一次git的命令,
git status #查看文件、目录在工作区间和暂存区中的状态。
git diff #命令用于比较工作区和缓存区的区别。
git diff --staged #比较的缓存区和本地仓库的区别。
git diff #查看工作目录同本地仓库指定commit的内容的差异,=HEAD时:查看工作目录同最近一次commit的内容的差异。
git diff #本地仓库任意两次commit之间的区别。
git show #查看commit信息及修改的所有内容
git show --stat #查看commit信息及修改的所有文件
git diff --stat #只查看commit修改的所有文件
git remote -v #查看当前本地仓库与哪些远程仓库连接
git remote show [remote] #显示某个远程仓库的信息
# 该命令可以在“2.4 代码回退”中使用
# 如果不小心回退了最新的commit,可以通过这个命令找到commit_id,从而再次让HEAD指向commit_id
git reflog #记录了每一次git的命令
软件开发中,总有无穷无尽的新的功能要不断添加进来。添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个功能分支,在上面开发,完成后合并,最后再删除该功能分支。
#=== 列出分支 ===#
git branch #列出所有本地分支。
git branch -r #列出所有远程分支。
git branch -a #列出所有本地分支和远程分支
#=== 新建分支 ===#
git branch [branch-name] #新建一个分支,但是依旧停留在当前分支
git checkout -b [branch] #新建一个分支,并切换到该分支
git branch [branch] [commit] #新建一个分支,指向指定commit
git branch --track [branch] [remote-branch] #新建一个分支,与指定的远程分支建立追踪关系
#=== 切换分支 ===#
git checkout - #切换到上一个分支。
git checkout [branch-name] #切换到指定分支。
#=== 合并分支 ===#
git rebase [branch] #合并指定分支到当前分支
git merge [branch] #合并指定分支到当前分支
--no-ff #加上该参数就可以查看到合并分支图,查看方式:git log --graph
# dev_1和dev_2会往master上合,用git rebase和git merge分别合入后,master变化区别
---E---F---> dev_1
|
A---B------> master,是当前分支
|
---C---D--->dev_2
# git rebase dev_1
# git rebase dev_2
A---B---E---F---C---D---> master # 按照commit时间排
# git merge dev_1
# git merge dev_2
A---B-------------------G---> master # 按照merge合入次序排
| |
---E---F---C---D---
# 如果再执行:git rebase -i commit_B,效果与git rebase合并两分支就一样了
# rebase步骤:先合并 -> 解决冲突 -> git add -> git rebase --continue -> git push
# merge步骤:先合并 -> 解决冲突 -> git add file-> git commit-> git push
#=== 删除分支 ===#
# Git会自行负责分支的管理,所以当我们删除一个分支时,Git只是删除了指向相关提交的指针,但该提交对象依然会留在版本库中。
git branch -d [branch-name] #删除一个没有被打开的分支
git branch -D #删除一个正打开的分支
git branch #在已知提交的散列值的情况下恢复某个分支
tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
# 查看标签
git tag # 列出所有标签名,按字母排序
git show <tagname> #查看tag信息及其所对应的commit的信息
# 创建标签
git tag <name>
git tag v1.0 #默认标签是打在最新提交的commit上的
git tag v1.0 commit_id #在指定的commit上打标签
git tag -a name -m "tag message" commit_id #-a指定标签名,-m指定说明文字
# 推送标签
git push origin <tagname> #推送指定标签到远程
git push origin --tags #一次性推送全部尚未推送到远程的本地标签
# 删除标签
git tag -d <tagname> #删除一个本地标签
git push origin :refs/tags/<tagname> #删除一个远程标签(先删除本地)
# 删除已经推送至远程的标签v0.1
# git tag -d v0.1
# git push origin :refs/tags/v0.1
在切换分支的时候,如果工作区内容有变化,我们是无法切换分支的。
当我们从master拉出一个分支branch_1,开始在branch_1上工作并写了一些代码。此时如果master上面发现了一个紧急的bug,我们要暂停手上的活去定位master上的bug,但是我们的本地的branch_1分支已经有了我们新写的代码,我们需要将这些新写的代码保存到某个地方,然后让branch_1分支的代码和master上的代码一样,再去解决问题。
git stash
就是保存这些代码的一个地方,其结构是个栈,将当前未提交的修改(即,工作区的修改和暂存区的修改)先暂时储藏起来,这样工作区干净了后,就可以切换切换到master分支下拉一个branch_2分支。在完成紧急bug的修复工作后,将branch_2分支合入master分支,然后重新切换到branch_1分支下通过git stash pop
命令将之前储藏的修改取出来,继续进行新功能的开发工作。(参考链接)
git stash list #查看所有的stash
git stash show -p stash@{num} #查看某个stash具体内容
#把所有未提交的修改(包括暂存的和非暂存的)都保存起来,
#save_message是描述存储代码的信息,方便恢复的时候区分,还是很有必要
git stash save "save_message"
git stash pop #将缓存堆栈中的第一个stash删除,并将对应修改应用到当前的工作目录下
git stash apply stash@{num} #将缓存堆栈中的stash多次应用到工作目录中,但并不删除stash拷贝
git stash drop stash@{num} #删除指定stash@{num}
git stash clear #删除所有缓存的stash
将master分支上的bug修复了以后,我们要想一想,branch_1分支是早期从master分支分出来的,所以,这个bug其实在当前branch_1分支上也存在。那怎么在branch_1分支上修复同样的bug?
git cherry-pick commit_id
。初始化一个仓库,将修改该的代码全部提交到GitHup上。
git init
git add .
git commit -m "messenge"
git remote add origin [url]
git push -u origin br_name #第一次推送需要-u,后续不再需要
-u Git不但会把本地的br_name分支内容推送的远程新的br_name分支,还会把本地的br_name分支和远程的br_name分支关联起来
现在有以下三条提交内容,前两条commit是我们推上社区的,第三条是社区已经合入的最新commit。
# 我们提交的最新一次commit,就叫commit_1
commit 6675bf2fbd96ca755c8a2610c50fd1ac600c5d44 (HEAD -> master, tag: master.mailed)
...
Change-Id: Ie80dbae738f60df6c11a3fe31fc57de817d76afc
# 我们提交的倒数第二新commit,就叫commit_2
commit 04c7e2f6ddc00e881d9f26dd6acadd744f650ce2
...
Change-Id: I7ff3c8df24ed7990fe104bc2530354c0bd5fe018
# 远程仓库中合入的最新commit,就叫commit_3
commit b37c0602cdc9b7f13b3d539663e68b12f10b44b1 (origin/master, origin/HEAD)
Author: qmuntal .com>
Date: Mon Mar 13 14:34:38 2023 +0100
...
假设现在我们commit了两次,也就是打算用两个补丁将内容推上社区。社区检查发现,commit_1的某个文件file_1代码和提交描述信息都不对,我们可以按照下面的方式来修改。
#修改文件file_1代码
git add file_1
git commit --amend #修改描述信息,保存退出
git push ... #将这次对commit_1的修改推上远程仓库
我们推上社区后,检查又发现,commit_2的描述信息有误,所以我们需要在本地修改,然后再向上推。
git rebase -i b37c0602cdc9b7f13b3d539663e68b12f10b44b1 #commit_3
#命令执行完后会自动打开一个类似于下面的文件,将commit_2的pick改成reword,然后保存退出
reword 350ce6d57b ... #commit_2
pick effb5f88d7 ... #commit_1
...
# 上面操作退出后,又弹出一个新的框,让我们更改commit信息,编辑完后退出就好了
git push ... #推上远程仓库
推上社区后,检查又发现,commit_2的某个文件file_2不对,所以我们需要在本地修改,然后再向上推。
#修改文件file_2代码
git add file_2
git commit --fixup 04c7e2f6ddc00e881d9f26dd6acadd744f650ce2 #commit_2
git rebase --autosquash -i b37c0602cdc9b7f13b3d539663e68b12f10b44b1 #commit_3
#上面命令执行完后会自动打开一个文件,不用改任何东西,然后保存退出
...
git push ... #推上远程仓库
社区检查又发现,commit_2的某个文件file_2代码也不对,我们修改提交方式和commit_1中采用的第二种方式相同,只要将commit_1换成commit_2即可。如果commit_2的文件和描述信息都有误,我们可以按照下面方式来修改提交,这个就是前面两种方式的结合。
#修改文件file_2代码
git add file_2
git commit --fixup 04c7e2f6ddc00e881d9f26dd6acadd744f650ce2 #commit_2
git rebase --autosquash -i b37c0602cdc9b7f13b3d539663e68b12f10b44b1 #commit_3
#命令执行完后会自动打开一个类似于下面的文件,将commit_2的pick改成edit,然后保存退出
edit 350ce6d57b ... #commit_2
pick effb5f88d7 ... #commit_1
...
git commit --amend #开始对commit_2的描述信息修改,然后保存退出,这条命令可以多次使用
git rebase --continue #等commit_2的描述信息修改该好了,最后再执行这条命令
git push ... #推上远程仓库
继续使用5.2中的三次commit信息,现在我们要将commit_1和commit_2这两条commit信息合并为一条新的commit。可以下面的做法来完成:
git rebase -i b37c0602cdc9b7f13b3d539663e68b12f10b44b1 #commit_3
#命令执行完后会自动打开一个类似于下面的文件,将commit_1的pick改成squash,然后保存退出
pick 350ce6d57b ... #commit_2
pick effb5f88d7 ... #commit_1
pick .... #如果三个压缩一个,继续pick改成squash
# 上面操作退出后,又弹出一个新的框,让我们更改commit信息,编辑完后退出就好了
修改的内容尚未commit时,打patch以及将patch打入到项目中的方法如下:
#生成patch文件
git diff ... > xxx.patch
#将patch打入到项目
patch -pN -i patch_path/xxx.patch
# -p 指定patch文件中除去几层目录,也就是去掉几个/,如其中某个路径为/src/cmd/compile/internal/ssa/regalloc.go
# -p0 使用完整路径名,/src/cmd/compile/internal/ssa/regalloc.go
# -p1 去掉一个/,src/cmd/compile/internal/ssa/regalloc.go
# -p4 除去4个前导斜杠和前三个目录,internal/ssa/regalloc.go
# -i patch-file,不用这个参数也可以:patch -pN < patch_path/xxx.patch
用git format-patch HEAD
生成的patch也可以patch -pN -i xxx.patch
打入工作区代码。修改的内容已经commit时,打patch以及将patch打入到项目中的方法如下(参考链接):
# 生成patch文件,有几个^就会打几个patch,从最近一次打起
git format-patch HEAD^ #为最近一次commit打patch,有几个^就打包最近几个patch的内容
git format-patch -n1 -n2 #打包版本n1与n2之间的patch
git format-patch commit_id #commit_id(不含)之后的所有patch
git format-patch -n commit_id #commit_id(含)之前的n次commit打patch
git format-patch commit_id1..commitid2 #(commit_id1, commitid2]之间的所有commit打patch
git format-patch xxx --stdout > xxx.patch #将所有patch输出到一个指定位置的指定文件
# 打入patch
# 方式1:打入到工作区,不会生成comit
git apply --reject xxx1.patch xxx2.patch ...
#自动合入 patch 中不冲突的代码改动,同时保留冲突的部分
#这些存在冲突的改动内容会被单独存储到目标源文件的相应目录下,以后缀为 .rej 的文件进行保存
--stat # 查看patch中修改的文件
--check # 测试patch是否能应用到当前分支
find -name *.rej
#查看所有存在冲突的源文件位置
#逐个手动解决冲突,然后删除这些 *.rej 文件
# 方式2:打入到本地仓库,会生成commit
git am xxx1.patch xxx2.patch ...
#将patch打入到项目,如果遇到冲突,会暂停不再继续打入后续patch
#此时我们依然处于git am命令的运行环境中
git am --show-current-patch #查看失败的patch内容
# 如果冲突有必要解决,手动解决完冲突后
git add . #将所有改动都添加到暂存区
git am --continue #git add冲突代码后执行,继续打入后续patch(也可执行git am --resolved)
# 如果这个冲突没必要解决,直接跳过,继续打入后续patch
git am --skip #跳过此次冲突
# 如果想恢复打入patch之前的代码
git am --abort #回退打入patch的动作,还原到操作前的状态