从0开始学习Git操作
目录
- 什么是Git
- Git的相关术语
- 使用Git基本命令
- Git命令实战
什么是Git?
Git是分布式版本控制系统,和其他版本控制系统的主要差别在于Git只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。 Git并不保存这些前后变化的差异数据,Git更像是把变化的文件快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件做快照,然后保存一个指向这次快照的索引。
基本的Git工作流程如下:
- 对工作目录中修改某些文件
- 对修改后的文件进行快照,然后保存到暂存区域
- 提交更新,将保存在暂存区域的文件快照永久转存到Git目录中
Git的相关术语
- 工作区(workspace):进行开发改动的地方,是当前看到的,也是最新的。平常我们开发就是拷贝远程仓库中的一个分支,基于该分支进行开发,该开发过程中就是对工作区的操作。
- 暂存区(Index):.git目录下的index文件,暂存区会记录 git add 添加文件的相关信息(包括文件名、大小、timestamp...),不保存文件实体,通过id指向每个文件实体。可以使用git status查询暂存区的状态。暂存区标记了你当前工作区中哪些内容是被git管理的。当你完成某个需求或功能后需要提交在远程仓库,那么第一步就是通过 git add 先提交到暂存区,被git管理。
- 本地仓库(repository):保存了对象被提交过的各个版本,比起工作区和暂存区的内容,它更旧一些。git commit后同步index的目录树到本地仓库,方便从下一步通过git push同步本地仓库与远程仓库的同步。
- 远程仓库(remote):远程仓库的内容可能被分布在多个地点的处于写作关系的本地仓库修改,因此它可能与本地仓库同步,也可能不同步,但它的内容是最旧的。
Git的基本命令
先上一张Git常用命令速查表,也是我的桌面背景,很Nice~
Git命令实操-从0开始走遍所有命令
- 安装Git:git-scm.com/downloads
- 配置账号:因为Git是分布式版本控制系统,所以每个机器都要自报家门
# lobal参数表示会对这台机器上所有的Git仓库都会使用这个配置 git config --global user.email 'yourEmail' git config --global user.name 'yourName' # 配置完成后可以使用 git config --list 命令查看
- 创建版本库:版本库又名仓库(repository),即一个文件目录,这个目录里的所有文件都可以被Git管理,每个文件的修改、删除,Git都能追踪,可以追踪历史、可以还原到某个历史版本。
# 1.创建一个空目录 mkdir learngit cd learngit # 2.将该目录变更为Git可以管理的仓库,可以使用ls -ah查看隐藏目录 git init # 3.创建一个readme.txt文件 echo -e "I love study, and day day up~" >> readme.txt # 4.将文件添加到暂存区 (工作区->暂存区->本地仓库->远端仓库) git add readme.txt # 5.将文件提交到本地仓库 git commit -m 'wrote a readme file' # 6.成功提交readme.txt之后,再次修改readme.txt文件 cat > readme.txt << end git is a distributed version control system. git is free software. end #注:'>' 是直接覆盖文件内容,'>>' 是在文件末尾追加内容; # cat > readme.txt << end :是持续往readme.txt文件中输入内容,遇到'end'时结束。 # 7.在修改了一些文件之后并没有添加到暂存区之前,可以使用git status命令查看进行过修改但还没有提交的文件 git status 输出: On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a") # 8.还可以使用git diff命令查看文件具体进行了哪些修改 git diff readme.txt 输出:(add one row是我自己加的,可忽略) diff --git a/readme.txt b/readme.txt index 12a297d..fa5593d 100644 --- a/readme.txt +++ b/readme.txt @@ -1 +1,3 @@ -I love study, and day day up~ +git is a dsitributed version control system. +git is free software +add one row # 9.将修改后的readme.txt文件添加到暂存区,然后再次使用git status查看(可跟add之前的git status 的结果进行对比,看什么区别?) git add readme.txt git status 输出:(含义:将要被提交的文件有:readme.txt,可在commit之前进行核查) On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: readme.txt # 10.提交修改后,再次git status查看仓库当前状态 git commit -m 'add distributed' git status 输出: On branch master nothing to commit, working tree clean # 小结:随时掌握工作区的状态,使用git status;然后git diff可查看修改内容。
- 版本回退
# 1.再次修改readme.txt文件 cat > readme.txt << end git is a distributed version control system. git is free software distributed under the GPL. end # 2.提交修改 git add readme.txt git commit -m 'append GPL' # 3.查看所有历史提交 git log 输出: commit 2eac6c0ff4a4533874cefb2f5a4bc02210849c3c (HEAD -> master) Author: zhangchao56 <zhangchao56@baidu.com> Date: Fri Nov 27 15:47:02 2020 +0800 append GPL commit a11e1f97a11376ee35078424d8a3b3dd4a6e05c2 Author: zhangchao56 <zhangchao56@baidu.com> Date: Fri Nov 27 15:38:40 2020 +0800 add distributed commit 35f7058de3f3e4943dc2cb5ee1f0fea1541a26a2 Author: zhangchao56 <zhangchao56@baidu.com> Date: Fri Nov 27 15:14:35 2020 +0800 commit readme.txt # 3.1 可以加上 --pretty=oneline 来简化输出信息 git log --pretty=oneline 输出: 2eac6c0ff4a4533874cefb2f5a4bc02210849c3c (HEAD -> master) append GPL a11e1f97a11376ee35078424d8a3b3dd4a6e05c2 add distributed 35f7058de3f3e4943dc2cb5ee1f0fea1541a26a2 commit readme.txt # 4.版本回退命令参数解释: # git reset --hard HEAD^ (HEAD^^、HEAD^^、HEAD~100)分别什么含义? # HEAD 表示当前版本;HEAD^表示上一个版本;HEAD^^表示上上个版本;HEAD~100表示往上100个版本; # --hard # 4.1 将版本回退到上一个版本,即'add distributed' git reset --hard HEAD^ git log 输出: a11e1f97a11376ee35078424d8a3b3dd4a6e05c2 (HEAD -> master) add distributed 35f7058de3f3e4943dc2cb5ee1f0fea1541a26a2 commit readme.txt 可见到HEAD已指向'add distributed'版本,回滚版本成功! # 4.2 将版本再回到刚刚回退前的那个版本,即'append GPL' 如何找到刚才的版本号?翻终端记录或者使用 git reflog 来查看版本记录 git reflog 输出: a11e1f9 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^ 2eac6c0 HEAD@{1}: commit: append GPL a11e1f9 (HEAD -> master) HEAD@{2}: commit: add distributed 35f7058 HEAD@{3}: commit (initial): commit readme.txt # 将HEAD指向 'append GPL' 版本:2eac6c0 git reset --hard 2eac6c0 git log --pretty=oneline 输出: 2eac6c0ff4a4533874cefb2f5a4bc02210849c3c (HEAD -> master) append GPL a11e1f97a11376ee35078424d8a3b3dd4a6e05c2 add distributed 35f7058de3f3e4943dc2cb5ee1f0fea1541a26a2 commit readme.txt 又回到最初的版本~ git reflog 查看版本操作记录 输出: 2eac6c0 (HEAD -> master) HEAD@{0}: reset: moving to 2eac6c0 a11e1f9 HEAD@{1}: reset: moving to HEAD^ 2eac6c0 (HEAD -> master) HEAD@{2}: commit: append GPL a11e1f9 HEAD@{3}: commit: add distributed 35f7058 HEAD@{4}: commit (initial): commit readme.txt # 小结:HEAD 指向当前版本;git log查看提交历史;git reflog 查看命令历史。
- 工作区和版本库:
Git 相对其他版本控制系统如SVN的不同之处就是有暂存区的概念; 工作区:就是咱们刚开始创建的learngit文件夹,相当于是工作区; 版本库:工作区中的 .git 目录就是Git的版本库。暂存区也是版本库中的一部分。
- 管理修改:Git跟踪并管理的是修改,而非文件,下面做个演示
# 1.对readme.txt做修改,并 add 到暂存区 cat > readme.txt << end update content.. end git add readme.txt # 2.再次对readme.txt做修改,不 add cat >> readme.txt << end update content again.. end 此时:git status 输出: On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: readme.txt Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: readme.txt # 3.将修改提交到本地仓库 git commit -m 'commit update' # 4.提交后,再次查看状态 git status 输出: On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a") 可以看到,第二次的修改并没有被提交,为什么? git commit只是将暂存区的修改提交到仓库,但是没有进入到暂存区的修改是无法被提交的,因此第二次修改没有经过 add 到暂存区的话,是不会被提交到仓库的 # 5.将工作区的文件内容和仓库中的文件内容对比查看 git diff HEAD -- readme.txt 输出: diff --git a/readme.txt b/readme.txt index 109e19d..50b4a5b 100644 --- a/readme.txt +++ b/readme.txt @@ -1 +1,2 @@ update content.. +update content again.. 发现,第二次的修改确实没有被提交。 小结:没有被 add 到暂存区的修改,是不会参与 commit 的。
- 撤销修改:如何将工作区的修改回到最近一次 git commit 或 git add 时的状态
git checkout -- readme.txt ( -- 很重要,没有就编程 切换分支 的命令了) 的作用? # 修改没有 add 到暂存区时,撤销修改,就会回到和版本库一样的状态 # 修改已 add 到暂存区,撤销修改,就会回到添加到暂存区后的状态 # 演示1:修改还未添加到暂存区 git status 输出: On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a") cat readme.txt 输出: update content.. update content again.. git checkout -- readme.txt git status 输出: On branch master nothing to commit, working tree clean cat readme.txt 输出: update content.. # 演示2:修改已添加到暂存区 git status 输出: On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: readme.txt cat readme.txt 输出: update content.. add a row 使用git reset 命令可以将暂存区的修改撤销掉,并重放到工作区 git reset HEAD readme.txt 再次查看状态,发现 修改 已回退到工作区 git status 输出: On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a") 再撤销掉本地的修改 git checkout -- readme.txt 查看状态 和 文件内容 git status 输出: On branch master nothing to commit, working tree clean cat readme.txt update content..
- 删除文件
当你删除工作区文件后:rm readme.txt 查看状态:git status 输出: On branch master Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) deleted: readme.txt no changes added to commit (use "git add" and/or "git commit -a") 如果你误删了工作区,那可以使用 git checkout -- readme.txt 来撤销本地的操作,即可恢复被误删的文件 如果你确实要删除,则可以使用 git rm readme.txt 来删除版本库中的该文件,再提交即可 git rm readme.txt git commit -m 'remove file' 小结:git rm用于删除一个文件; 如果一个文件已经被提交到版本库,误删之后就可以恢复,但是会丢失最近一次提交的修改。 git 真的牛皮
- 远程仓库
# 如果要跟远程仓库进行交互,则需要设置公钥; 一路回车、用默认选项 ssh-keygen -t rsa -C 'youremail@xxx.com' # 一切顺利,最后会在.ssh下生成id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露;id_rsa.pub是公钥,是需要配置到第三方平台的,如github # 将公钥内容复制到粘贴板,配置到对应的地方 cat ~/.ssh/id_rsa.pub | clib # Windows cat ~/.ssh/id_rsa_pub | pbcopy # MacOS
- 创建与合并分支
# 创建dev分支 -b:表示创建并切换 git checkout -b dev # 在dev分支下对readme.txt文件做修改并提交 echo -e 'add one row..' >> readme.txt git add readme.txt git commit -m 'create dev' # 切换master分支,并将 dev 分支的改动合并到 master 上 git checkout master // 切换到master分支 git merge dev // 将dev分支的改动合并到当前分支 输出: Updating 664c79d..44d86fd Fast-forward readme.txt | 1 + 1 file changed, 1 insertion(+) 其中 Fast-forward 代表这次合并是“快进模式”,即直接将master指向dev的当前提交,所以合并非常快。也存在 合并需要解决冲突 的情况 # dev的修改合并到master分支之后,删除dev分支 git branch -d dev //只是删除本地分支 git checkout <branch> 和 git checkout -- <file> :一个命令两种作用,让人不得其解~ 新版本的 git 提供了新的 git switch 来切换分支,操作如下: # 创建并切换到新的dev分支 git switch -c dev # 切换到已有的分支 git switch dev 小结一下: 查看分支:git branch -vva //推荐使用,信息全 创建分支:git branch <name> 切换分支:git checkout <name> 或 git switch <name> 创建+切换分支:git checkout -b <name> 或 git switch -c <name> 合并某分支到当前分支:git merge <name> 删除本地分支:git branch -d <name> 强制删除本地分支:git branch -D <name> 删除远程分支:git push --delete origin <name>
- 解决冲突(重要)
# 准备合并冲突条件:两个分支在同一个文件的同一处都进行了修改,在合并时就会提示冲突 git switch -c feature //创建一个新分支 vim readme.txt //修改最后一行,随便改点啥 git add readme.txt git commit -m 'feature update laster row' git checkout master //切回master分支 vim readme.txt //修改最后一行,随便改点啥 git add readme.txt git commit -m 'master update laster row again' # 条件已准备完毕,feature和master分支都各自分别有新的提交 git merge feature //将feature上的更新合并到当前分支 输出: Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result. 提示内容:readme.txt文件存在冲突,自动合并失败,需要手动解决冲突后再提交 git status 输出: On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a") # 开始手动去解决冲突 vim readme.txt 输出: this is reamme.txt <<<<<<< HEAD udpate laster row again... ======= update laster roe >>>>>>> feature # 假设以master分支的修复为准,则手动删掉feature的更新,如下: this is reamme.txt udpate laster row again... # 修复完冲突后,再次提交完事 git add readme.txt git commit -m 'conflict fixed' 至此,冲突就解决完毕并提交,可以查看分支的合并情况 git log --graph --pretty=oneline --abbrev-commit 输出: * bac03a9 (HEAD -> main) conflict fixed |\ | * 1f136f3 (feature) feature update laster row * | 42c82f9 main update last row again |/ * 44d86fd (origin/main) create dev * 664c79d create readme.txt * 2c70154 remove readme.txt * 480004b commit update * 2eac6c0 append GPL * a11e1f9 add distributed * 35f7058 commit readme.txt 顺便解析下上命令的参数: --graph :最左边的那个轨迹图就是该参数的作用,即分支合并图 --pretty=oneline:设置每个提交历史展示一行 --abbrev-commit:设置commit id精简,大概7位 # 完事后,删除feature分支 git branch -d feature
- 分支管理策略:
# 在合并分支时,如果采用的Fast forward模式,在删除分支后,就会丢掉分支信息。 如:在dev分支的修改合并到master分支后,后边删除了dev分支,在提交历史上完全看不出曾经在dev上做过修改。 虽然我也想不到能看出在哪个分支上做过修改有啥实质意义? # 那么如何避免这种情况呢? 可以在合并的时候,强制禁止 Fast forward 模式,Git就是在merge时生成一个新的commit,这样,从提交历史上就可以看出分支信息 # 实战演示一波: git switch -c dev //新建dev分支 echo -e 'delete readme.txt last row' >> readme.txt git add readme.txt git commit -m 'delete readme.txt last row' git switch master //切到master分支 # 合并分支 --no-ff参数表示禁用Fast forward; 因为是创建一个新的commit,所以加上 -m 参数,添加描述 git merge --no-ff -m 'merge with no-ff' dev # 查看分支历史 git log --graph --pretty=oneline --abbrev-commit 输出: * 4f2fefe673f2b329a81a168a33204f3564258d01 (HEAD -> main) merge with no-ff |\ | * 5fe3706d4cc74d248065ab942669999855334309 delete readme.txt last row |/ * e14aa9fb0c84aa3a50a364f2ec909624995028bf delete row * bac03a9d926f68e3d37efeedb9f8592ba2f1c1ef conflict fixed 小结: 合并分支时,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支,能看出来曾做过合并,而 fast forward 合并方式就看不出来曾经做过合并
- Bug分支
# 在日常工作中,我们经常会遇到这样的问题:你正在dev分支进行开发,然后突然接到一个线上bug的修复任务,命名为101,dev分支有你写了大半天的代码,但是还没有一半的任务没有完成,这时怎么处理? 如下: git status //还有一半的任务未完成 输出: On branch dev Untracked files: (use "git add <file>..." to include in what will be committed) task.txt nothing added to commit but untracked files present (use "git add" to track) # 提交代码也不合适,功能还不完整,但是必须要快速解决线上bug,使用 stash 将当前现场 存储 起来 git stash save '先将修改存下来,待会再拿出来' 输出: Saved working directory and index state On dev: 先将修改存下来,待会再拿出来 # 然后切到master分支,新建 bug fix 分支来做修复 git checkout master //切master分支 git checkout -b issue-101 //新建fix分支 echo -e 'fixed bug' >> readme.txt //修复bug git add readme.txt git commit -m 'fix bug-101' git switch master git merge --no-ff -m 'merge bug fix 101' issue-101 //合并在issue-101上做的bug fix # 至此,bug已经修复完成,并合并到master分支,然后回到dev分支继续干活 git switch dev git status //此时工作区是干净的,dev之前的改动已经被存储起来了 输出: On branch dev nothing to commit, working tree clean git stash list //显示保存列表 输出: stash@{0}: On dev: 先将修改存下来,待会再拿出来 git stash pop //从列表中恢复第一个存储记录,并删除记录 或 git stash apply stash@{0} //可以恢复指定的stash # 至此,就回到dev之前的开发环境状态。那在master分支上做的bug fix 在dev分支上也存在,如何在dev也fix掉呢? 最简答的方法:将master bug fix的那次提交复制到dev分支 git cherry-pick commitId(这个是bug fix 那个提交的ID) //cherry-pick,复制一个特定的提交到当前分支,是产生一个新的提交,并不是同一个 输出: [dev 1afa841] fix bug-101 Date: Mon Nov 30 15:55:20 2020 +0800 1 file changed, 1 insertion(+) # 至此,在master上所做的bug fix 也同步到dev分支了 小结: 修复bug时,通常是创建新的bug分支来进行修复,然后合并,最后删除; 当手头工作正在进行时,先把工作现场git stash一下,然后去修复bug,修复完成后,在git stash pop回到工作现场; 在master分支上修复的bug,想要合并到当前dev分支,可以用git cherry-pick <commit>命令把bug提交的修改复制到当前分支,避免重复劳动。
- Feature分支
这块没啥说的,就是一些习惯问题: 开发一个新feature,最好新建一个分支;
- 多人协作
多人协作的模式通常是: 1.首先,试图用 git push origin <branch-name>推送自己的修改; 2.如果推送失败,则因为远程分支比本地更新,则需要用git pull去拉取远程分支的更新; 3.如果有冲突,则解决冲突,并在本地提交; 4.没有冲突或解决掉冲突后,再用git push origin <branch-name>推送就会成功 注:如果git pull 提示 no tracking information,则说明本地分支和远程分支的连接关系没有创建,用命令 git branch --set-upstream-to <branch-name> origin/<branch-name> 小结: 查看远程库信息,使用git remote -v 本地新建的分支如果不推送到远程,对其他人不可见 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name
- Rebase用法(变基)
rebase操作可以把本地未push的分叉提交历史整理成直线
- 标签管理
发布一个版本时,通常先在版本库中打一个标签(tag)。 标签相当于是版本库的快照,本质是指向某个commit的指针。但是分支可以移动,标签不能移动 标签的意义是方便查找 # 创建标签 git tag dev-1.0 // git tag <name> 在最新的一次提交上打标签 git tag dev-1.1 commitid // 在某个提交上打标签 git tag -a dev-1.2 -m '标签描述' commitid //在某个提交上打标签并添加描述信息 git tag //查看所有标签(标签是按照字母顺序展示的) git show dev-1.0 //查看某个标签 # 操作标签 git tag -d dev-1.2 //删除标签 git push origin dev-1.2 //将标签推送到远程 git push origin --tags //一次性推送全部尚未推送到远程的本地标签 # 删除远程已推送到远程的标签 git tag -d dev-1.1 //先删除本地标签 git push origin :refs/tags/dev-1.1 //再从远程删除 小结: git tag <tagname> 用于新建一个标签,默认为HEAD,也可以指定一个commit id git tag -a <tagname> -m 'balabla' commitid 可以指定标签信息 git tag 查看所有标签 git push origin <tagname> 可以推送一个本地标签 git push origin --tags 可以推送全部未推送过的本地标签 git tag -d <tagname> 可以删除一个本地标签 git push origin :refs/tags/<tagname> 可以删除一个远程标签
- 忽略特殊文件
可以通过编写 .gitignore 来忽略某些文件 .gitignore 文件本身要放到版本库
- 配置别名
是否觉得 git log 展示的信息很繁琐? 但是手动输入 git log --graph --pretty=oneline --abbrev-commit 又很繁琐? 可以自定义配置: git config --global alias.l 'log --graph --pretty=oneline --abbrev-commit' 然后 git l 去看效果就好~
至此,我们就完整的将Git的常用操作都尝试了一遍,是不是有所收获~
加油吧,打工人!
转载自:https://juejin.cn/post/6900916524757090318