GIT原理和常用命令速成

【第三次更新,2025年2月16日】

如果使用http的方式,github已经不支持使用密码,而是需要去网页上建立token,利用token来代替原有密码

 

【第二次更新,2018-11-09,10:44:59】

几个好习惯:

checkout(检出or签出)之前git status,因为checkout 后工作区和暂存区文件不变,有时候checkout只是想看一下另一个分支的内容,而不小心修改了该分支

git stash pop之前git add  因为pop不能撤回,一旦pop错了会很麻烦(你就不知道哪些是pop出来的,哪些是自己修改的)

两个概念:<commit> and <branch> (图片来自网络)

中间哈希值ID表示的是提交,注意HEAD指针指向的是某次提交

上面master表示分支

图上这种HEAD指针指向的提交没有对应分支的状态,称作分离的头指针(detached head),这种状态只是偶尔会用到

 

【第一次更新,2018-05-25,13:29:16】

1、有关bare和 non-bare库

普通库(non-bare)使用 git init创建,在本地可以直接进行修改提交等等

裸库(bare)使用git init --bare创建,不存在和普通库一样的各种工程文件,只有.git文件,一般目录的取名都是XXX.git,主要用于多人同步开发时候使用(例如远程的github)

ps.有时候我们想要在本地的几台电脑上(或者服务器,虚拟机等等)协同开发,可能会需要从普通库clone 到一个普通库,还需要在新的库中pull push等

需要注意的几点:

(1)clone bare库和non-bare库的操作一样

(2)同一台电脑上,或者使用samba服务器远程登录下,可以把origin直接设置为本地路径,例如D:/123/123;不然就要使用ssh协议或者https协议

(3)向non-bare的库push之前要在这个non-bare库配置一下,不然默认是refused的。

指令为git config --local receive.denyCurrentBranch updateInstead(git文档

 

以下是原文


 

【原创,2018-03-16,15:33:49】

刚开始学习GIT的时候,以为只要掌握几个基本的操作指令就万事大吉了。但是随着GIT使用的深入,如果不了解些原理就会遇到各种各样让人一脸懵逼的问题,所以还是要对GIT原理有一些基本了解,这里默认已经掌握常用的基本指令了。

 

一、GIT原理

GIT是什么?一个版本控制系统。

先从git分支图开始,举例子:

这是git中的树状图,表示了版本的演进过程,每个节点称为一次commit。下方的节点表示父节点,上面的节点表示子节点,子节点由父节点演化得到。

1、每一个圆点表示一次提交commit(表示一个版本),每个commit用哈希值来唯一标识ID,右面的文字表示commit时写的注释信息

2、右侧方框表示分支branch名称(例如master, average等),所连接的节点表示该分支当前处在的版本。

3、黄色表示HEAD指针(表明当前工作区的版本)对应的节点

4、每一个仓库包括:工作区、版本库, 如图所示(图片来自网络)

图片来自网络

工作区:对应于当前仓库(repository)目录(linux叫目录,win叫文件夹)下除了.git文件夹外其他所有文件(也不包括exclude文件中列举的文件)

版本库:对用于.git中的文件,包括暂存区(stage/index)的数据 + 所有历史版本的数据

5、数据分为三种状态:unstaged(修改中), stage(保存修改),  commit(已提交),他们的转换关系如图。

 


 

 二、常用指令

0、github初始配置

(1)生成秘钥对ssh-keygen -t rsa -C "your_email@youremail.com"

(2)github添加公钥

(3)设置当前用户信息(用于标识每个commit的用户信息,;global看情况选择,global是所有的git仓库都配置,无global参数则只配置当前仓库)

$ git config --global user.name "your name"
$ git config --global user.email "your_email@youremail.com"

git config core.autocrlf true(windows)/input(linux) 换行符配置参考下文

git config core.safecrlf warn 如果本地的换行符与git设置不相符,则告警

如果只用http协议(每次使用需要去github上产生一个短期密码,适合临时使用一次)而不用ssh(设置好秘钥以后使用就很方便),可以跳过前两步

 

1、GIT从零开始

(1)从远程主机下载已有的仓库开始

注意,git clone下来之后默认只会在本地建立一个分支master

(不管远程库有几个分支,本地建立的分支对应于remotes/origin/HEAD指向的分支,其他分支的信息也是会下载下来并通过隐藏的方式保存,如果想查看所有分支,需要使用git branch -a。如果需要本地建立其他的分支,需要手动创建(或者git checkout -t origin/XXX 自动创建同名分支)。

a)ssh方式

git clone git@域名:用户ID/仓库名称.git

b) https方式

git clone https://域名/用户ID/仓库名称.git

输入用户名和密码

(2)从本地主机新建仓库开始

git init

 

2、本地分支的操作

git branch 查看本地分支

git branch -a 查看本地分支和远程分支

git checkout -b AAA BBB 在BBB节点(提交ID号或者分支名均可)上建立名字为AAA的分支并切换到该分支

(缺省BBB:默认使用HEAD指针的节点)

git checkout -t BBB 建立与BBB分支同名的本地分支(此时的BBB分支只能是远程分支全名,例如remotes/origin/master)

  • git branch -d AAA 删除名字为AAA的分支(删除没有被合并过的分支要用-D参数)如果某个节点没有在一个分支上,一段时间后git就可能把该节点删除掉了,所以不要仅使用commit ID而不建立分支

git checkout AAA HEAD指针移动到已存在的分支AAA

  • 如果unstaged和staged的文件与将要移动的分支不冲突的话会直接带入到新的工作区, checkout之前记得提交或者放入栈(参考下文)

 

2.5 checkout命令

(1)切换版本(移动HEAD指针)

git checkout <branch/commit> 

(2)将某个版本的某个文件提取出来,到暂存区

git checkout <branch>/<commit> -- filename

  • 即之前工作区该文件的所有修改被取代

git checkout -- filename

缺省则表示HEAD指向的当前节点,即这条命令常用于撤销工作区该文件的修改

git checkout -p -- filename p参数标识可以按照文件某行去选择撤销修改,而不是文件的全部修改都撤销

(3)新建分支

参考上面的2

 

3、完成本次开发准备本地提交

(git status 查看文件状态)

git add 文件名/.(点表示所有unstaged文件)保存修改

git commit -m "xxx 提交

撤销commit可以使用reflog(用于记录每次操作,可用于回退)

 

4、版本回滚reset

分为两种情况:

a)HEAD指向某个分支当前版本,可以理解为修改该分支的版本,并且移动HEAD指针到该节点

b)HEAD指向不是任何一个分支的当前版本(即分离头指针状态,detached HEAD),等价于移动HEAD指针

 

git reset --soft XXX 把当前分支移动到历史提交XXX上,工作区文件不变,版本库中有差异的文件直接放入到暂存区(index),即staged状态

 

git reset --mixed XXX (--mixed可缺省) 当前分支移动到历史提交XXX上,工作区文件不变,版本库中有差异的文件放入到unstaged状态

 

git reset --hard XXX 当前分支移动到历史提交XXX上,工作区文件也都全部还原到历史版本

用命令行查看分支树:

git log (-n 查看记录数) (--oneline 简洁显示)

git log XXX 只查看XXX文件的记录

  • reset不会立刻把git树中的节点删除掉,而是会保存一段时间
  • HEAD^表示HEAD的父节点,HEAD^^上两个父节点
  • git reset HEAD <filename>  表示撤销暂存区的某个add,工作区不变,HEAD可省略

5、修改同步到远程服务器push

  • origin表示该仓库对应的远程仓库,clone的库直接对应远程库,不过可以修改也可以建立新的源

git push origin AAA:BBB 把AAA分支推到远程BBB分支(缺省 “:BBB”:远程分支和本地分支同名,远程没有同名分支则新建;缺省“AAA”:删除远程BBB分支)

(缺省“AAA:BBB”:push HEAD指针指向的分支,远程分支和本地分支同名)

(缺省 "origin AAA:BBB"): 若源是惟一的,则origin可以省略,指令作用同上,缺省origin的前提是这个origin已经和本地分支绑定,如果没绑定用git push -u origin

 

6、从远程服务器拉回最新内容pull

(1)pull = fetch + merge

(2)pull一般用于本地已经存在的分支,用于同步

git pull origin AAA:BBB(AAA是远程分支,BBB是本地分支)

(缺省:BBB表示本地分支和远程分支同名,缺省AAA:BBB表示HEAD分支,origin唯一的时候也可以缺省)

(3)fetch把远程分支树的分支拉取到本地上,远程分支名字一般是remotes/origin/XXX

  • fetch会修改FETCH_HEAD指针,指向着每个远程分支的当前版本提交号

git fetch origin AAA:BBB

(4)git diff AAA BBB 比较AAA分支到BBB分支的变化,如果结果太长建议使用重定向,让结果输出到文件(例如git diff AAA:BBB > a.diff )

(缺省:BBB,表示AAA分支到当前工作区变化(包括非暂存区),例如git diff FETCH_HEAD)

checklist:

git diff  可以查看当前工作区内容修改(已经加入暂存区但是未提交的修改看不到,只能是工作区与 暂存区/提交之间的修改)

git diff --cached查看已经加入暂存区但没有提交的改动(在缓冲区的文件变化,与上一条互斥)

git diff HEAD 是上面两条命令的合并,即所有未提交的改动

git diff AAA 是从AAA分支到 工作区+暂存区的全部改动 (上一条基础上再添加 commit 之间的变更)

git diff AAA BBB 从AAA到BBB分之的修改

关于diff输出的标记符号:

参考:http://blog.csdn.net/zcube/article/details/42246331

补充:index 后面的哈希值是 file id,标识一个文件的一个版本(即使是工作区的修改文件)

补充:diff打印的路径是从 git 跟目录算起的,不管从哪里执行 git diff

(5)git merge AAA 表示AAA分支合并到当前分支(缺省AAA表示FETCH_HEAD分支合并到当前分支)

如果发生冲突,需要自己去文件中修改,文件中的标记如下:

<<<<<<<到=======是HEAD的文件内容,=======到>>>>>>>是被合并版本内容

 

7、栈

一般用于两种情况

(1)自己在本地上做了一些修改但由于没完成还不想提交,发现远程也做了一些修改,所以可以先把本地的修改加入到栈中,然后pull,在读出修改信息

(2)在开发中突然发现一个更重要的问题需要修改测试,可以把当前的部分没完成的工作加入到栈中

ps. 注意:暂存区的修改放入栈中并pop之后还是会进入到工作区

git stash show 当前栈中的所有条目

git stash show -p 栈中的所有修改(类似于git diff)

git stash -u把所有unstaged和staged的文件修改加入到栈中(没有-u新文件不加入栈中)

git stash pop 读出栈的修改(如发生冲突需要自己去修改)

git stash drop 扔掉栈中的第一条

git stash clear 清空栈

就先写到这里,其他的补充以后再添加

 

8、关于删除

(1)直接删文件

删除文件这一操作也是需要提交的,git会记录删除之前的所有文件修改信息

(2)git rm <filename>

 既删除文件,同时又删除git所记录过的该文件的所有版本信息,直接提交到暂存区(staged/index)

(3)git rm --cache <filename>

不删除本地文件,只提交删除记录到暂存区

典型使用场景:某些文件的修改被track到版本库中,但后来发现可能并不需要track这些文件的版本信息(例如build生成文件以及与IDE有关的文件.idea/.vscode等),提交删除到暂存区,然后将文件添加到.gitignore/EXCLUDE中

ps1 .gitignore与EXCLUDE的区别这里不在多说,自行网上搜索。

ps2.girignore和EXCLUDE的优先级没有已经track的文件高,例如某个文件已经被track,添加了ignore,git status也会识别该文件的变化。因此才需要先git rm --cache 再添加ignore

ps3. 发现某些文件被commit但是后悔了,想要ignore,已经commit的分支也是没办法改变的,只能重新ignore再commit,更新分支。

 

9、跨平台开发的换行符问题

WIN ---- '\n\r' ---- 0D0A ---- CRLF

UNIX/LINUX/OS X ---- '\n' ---- 0A ---- LF

MAC OS(老苹果系统) ---- '\r' ---- 0D ----CR(现在没人用这个了吧)

 

# 提交时转换为LF,检出时转换为CRLF
git config --global core.autocrlf true
# 提交时转换为LF,检出时不转换
git config --global core.autocrlf input
# 提交检出均不转换
git config --global core.autocrlf false

GIT默认是第一种,同时也可以设置下

# 拒绝提交包含混合换行符的文件
git config --global core.safecrlf true
# 允许提交包含混合换行符的文件
git config --global core.safecrlf false
# 提交包含混合换行符的文件时给出警告
git config --global core.safecrlf warn

 

10. git help XXX 某个指令的帮助文档

git XXX -h

tab自动补全

 

11. 关于origin

origin变量储存远端仓库url,git clone后默认给定的就叫origin

所有保存过的远端仓库(因为也可以一个本地代码对应好几个远端仓库)可以用git remote -v来查看

git remote就是设置远端仓库url的命令

git remote set-url new_origin XXX

 

12. 关于upstream

文档中说的upstream指的就是本地分支所track的远程分支,只有先关联才能同步

 

13. 子模块的使用

a.  新建子模块:

git submodule add [url]

b. 更新子模块(本地更新push远端)

就在子模块里正常add commit push就行了

主仓库如果需要提交也需要同样的一边操作

c. 远程更新,本地同步

在子模块里pull,或者在主仓库里git submodule update --remote (但是这样会使得子模块的本地分支不发生变化,同时HEAD指向remote/origin/XX分支)

d. clone一个包含子模块的仓库

git clone -----1

git submodule init -----2

git submodule update ------3

或者 

git clone --recurse-submodules ==》1+2+3+递归
git submodule update --init ==》2+3
git submodule update --init --recursive ==> 2+3+递归
git submodule update --init --recursive ==> 2+3+递归

 
14. 查看某一行的修改记录
git blame file -L startline<, endline> 可以找到commit id
 
15. 查看某次提交的记录
git show <commit id> -q 查看提交log
git show <commit_id> 查看本次提交的diff
git show <commit_id> --stat 查看本次提交对应的文件

16. 打patch(利用git diff的输出发送给同事,合并代码)
参考链接:https://blog.csdn.net/u013318019/article/details/114860407
重要:patch 文件的路径是从 git 根目录算起的,也需要在根目录执行合并命令,路径与自己的库对应不上考虑修改 patch 里面的路径,或者手动合并,或者是用 linux 的 patch 命令(参考:https://stackoverflow.com/questions/24821431/git-apply-patch-fails-silently-no-errors-but-nothing-happens)
方式一:直接git diff生成patch(参考上文git diff部分),然后利用git apply合并代码(也可以自己去读diff文件,参考上文链接)
一般的流程就是:
先 git apply --stat xxx.patch 检查一下这个 patch 文件(遗留:遇到过 报0 修改问题)
然后 git apply --check xxx.patch 检查一下能否直接合并这个 patch 文件
如果上一步没有任何打印,认为成功,直接 git apply xxx.patch就好
如果上一步有打印,直接 git apply --reject xxx.patch 强行打 patch,失败的地方会升成一个 xxx.rej 文件,解决途径参考上面链接。
解决冲突的一般流程:
先废弃之前已经有的合并,利用 git checkout,删除 rej 文件,修改源文件或者patch 文件,再从git apply --check 开始。
方式二:git format-patch 命令,只能生成某个提交到某个提交的diff,并添加了其他信息,例如修改用户名,文件编码等。然后利用git am合并代码
ps. git format-patch 是 commit 之间的操作,合并后直接生成新的 commit,而且支持一次性执行多个.patch 文件
ps. git apply甚至不需要再git库中运行,它只是根据diff文件去直接修改目标文件,产生冲突合并失败就得手动解决
使用流程比较简单,直接 git am xxx.patch(支持通配符*.patch,一次合并多个 patch 文件)
解决冲突一般路程:
先 git am --abort,然后与 git apply --reject 合并出问题的 patch,看.rej 文件(此时最好修改 rej 文件,修改源文件需要 commit 一次),废弃已有的合并,删除.rej 文件,再从git am开始
 

17. 关于文件的 filemode
git ls-tree <commit_id>会打印出文件的 filemode,用来标识文件类型与权限
有时候 git diff,有些文件内容没有被修改,但是显示 filemode 变了,大概率是文件的权限变了
参考链接:https://stackoverflow.com/questions/737673/how-to-read-the-mode-field-of-git-ls-trees-output

 
18. rebase和cherry-pick
(1) rebase是分支嫁接,从分叉的地方开始,整个分支迁移过去。在分支的末端运行命令
参考:https://backlog.com/git-tutorial/cn/stepup/stepup2_8.html 
(2) cherry-pick是把某一次commit的修改,应用于另一个commit
参考:https://blog.csdn.net/FightFightFight/article/details/81039050
 
 
 
 
posted @   牧羊人邱Sir  阅读(291)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!
点击右上角即可分享
微信分享提示