git 中容易遗忘的点!

604 阅读17分钟

现在大部分开发工具都可以直接进行菜鸟下面说的大部分操作,具体见:虽然理解git命令,但是我选择vscode插件!

推送/抓取 指定分支

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时要指定本地分支,这样Git就会把该分支推送到远程库对应的远程分支上:

$ git push origin master

如果要推送其他分支,比如dev,就改成:

$ git push origin dev

切换远程分支

多人协作时,大家都会往master和dev分支上推送各自的修改。

现在,模拟一个你的小伙伴A,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者 同一台电脑的另一个目录下克隆:

$ git clone git@github.com:pbw-langwang/gitskills.git
Cloning into 'gitskills'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

当A从远程库clone时,默认情况下,A只能看到本地的master分支。不信可以用 git branch 命令看看:

$ git branch
* master

现在,A要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支(前提是你的远程厂库有dev分支,不然这样是不行的):

$ git switch -c dev origin/dev

如果远程仓库没有dev分支,会报错:

invalid reference: origin/dev

解决办法是:先自己创建该分支,然后push,再回到这里使用就好了!

抓取分支报错

如果 git fetch origin <branch-name> 提示:

There is no tracking information for the current branch.Please specify which branch you want to merge with. 

说明本地分支和远程分支的连接关系没有创建,用下方命令创建关联:

git branch --set-upstream-to=origin/<branch-name> <branch-name>

注:本地和远程分支的名称最好一致

删除远程分支

git push origin --delete <branch-name>

bug分支

软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

当你接到一个修复bug的任务时,很自然地,你想创建一个分支issue1来修复它,但是,等等,当前正在dev上进行的工作还没有提交:

$ git status
On branch dev
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

并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

保存工作现场 stash

幸好,Git还提供了一个 stash 功能,可以把当前工作现场 “储藏” 起来,等以后恢复现场后继续工作:

$ git stash
Saved working directory and index state WIP on dev: c116d82 remove text

现在,用 git status 查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:

$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 7 commits.
  (use "git push" to publish your local commits)

$ git switch -c issue1
Switched to a new branch 'issue1'

现在需要修复bug,先把issue1.txt加入issue1里面,然后把readme.txt修改一下,最后提交:

$ git add issue1.txt

$ git commit -m "add issue1"
[issue1 08c9489] add issue1
 1 file changed, 1 insertion(+)
 create mode 100644 issue1.txt

$ git add readme.txt

$ git commit -m "three line add !"
[issue1 16c9541] three line add !
 1 file changed, 1 insertion(+), 1 deletion(-)

修复完成后,切换到master分支,并完成合并,最后删除issue1分支:

$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 10 commits.
  (use "git push" to publish your local commits)


$ git merge --no-ff -m "master and issue1" issue1
Merge made by the 'recursive' strategy.
 issue1.txt | 1 +
 readme.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 issue1.txt


$ git branch -d issue1
Deleted branch issue1 (was 16c9541).

注意:

  1. 即使分支删除了,分支上的tag依旧存在!

分支更多见:

  1. git 标签很重要

恢复工作现场

现在,是时候接着回到dev分支干活了!

$ git switch dev
Switched to branch 'dev'

$ git status
On branch dev
nothing to commit, working tree clean

工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看:

$ git stash list
stash@{0}: WIP on dev: c116d82 remove text

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

  1. 一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除
  2. 另一种方式是用git stash pop,恢复的同时把stash内容也删了
$ git stash pop
On branch dev
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")
Dropped refs/stash@{0} (9b22c8fe87367d20d47876b7546dc888a5e8f134)

再用git stash list查看,就看不到任何stash内容了:

$ git stash list

你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:

$ git stash apply stash@{0}

cherry-pick(复制一个特定的提交到当前分支)

在master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。

那怎么在dev分支上修复同样的bug?重复操作一次,提交?有没有更简单的方法?有!

同样的bug,要在dev上修复,我们只需要把这个 [issue1 08c9489] add issue1 和 [issue1 16c9541] three line add ! 提交所做的修改 “复制” 到dev分支。

注意:

我们只想复制这两个提交所做的修改,并不是把整个master分支merge过来。

为了方便操作,Git专门提供了一个 cherry-pick命令 ,让我们能复制一个特定的提交到当前分支

$ git cherry-pick 08c9489
[dev 35c4cea] add issue1
 Date: Tue Jul 14 14:52:23 2020 +0800
 1 file changed, 1 insertion(+)
 create mode 100644 issue1.txt


$ git cherry-pick 16c9541
[dev 8029720] three line add !
 Date: Tue Jul 14 15:09:11 2020 +0800
 1 file changed, 1 insertion(+), 1 deletion(-)

Git自动给dev分支做了一次提交,注意:这次提交的commit是35c4cea和8029720,它并不同于master的08c9489和16c9541,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在dev分支上手动再把修bug的过程重复一遍。

既然可以在master分支上修复bug后,在dev分支上可以 “重放” 这个修复过程,那么直接在dev分支上修复bug,然后在master分支上 “重放” 行不行?当然可以,不过你仍然需要 git stash 命令保存现场,才能从dev分支切换到master分支。

注意(菜鸟血的教训)

cherry-pick 不要去操作 merge 步骤! image.png 这样操作之后会让你提交不了,报错:

git pull --tags origin hotfix1.19.0
From http://xxxxx/it_develop_group/bn-lis/bn-lis-frontend
* branch              hotfix1.19.0 -> FETCH_HEAD
fatal: You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).
Please, commit your changes before you merge.

只能reset了

可能有别的办法,有懂的读者可以指点一下

版本回退和版本前进

版本回退

在Git中,用HEAD表示当前版本,也就是最新的提交d696468860aab706dfb385ea4c85a11a0e37d86f(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^ ,上上一个版本就是HEAD^^ ,当然往上100个版本写100个 ^ 比较容易数不过来,所以写成HEAD~100。

这里菜鸟建议,直接记下两个就好:一个是HEAD^;另一个就是HEAD~XX

现在我们要把当前版本回退到上一个版本,需要用到 git reset 命令,显示如下:

git reset --hard HEAD^
HEAD is now at d0e121c add four line

注意:

  1. --hard 参数:将 HEAD 指针、索引(暂存区)和工作目录中的文件全部重置为指定提交的状态 [如果有未提交的代码,千万不要加--hard,直接用git reset HEAD^即可,会用默认的--mixed会保留未提交的代码!]

  2. 如果是版本号的方式回退,而且正好有两个版本的的版本id一致,那么会跳转到离新版本最远的的一个

Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL:

在这里插入图片描述

改为指向add distributed:

在这里插入图片描述

然后顺便把工作区的文件更新了。所以你让 HEAD 指向哪个版本号,git 就把当前版本定位在哪!

版本前进

当你回退版本后,最近的版本已经变成了上一个版本,那这个时候想变回最新的版本怎么办?

例如:你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id怎么办?

在Git中总是有后悔药可以吃的。当你用 git reset --hard HEAD^ 回退到某个版本时,再想恢复到最新版,就必须找到最新版的 commit id。Git提供了一个命令 git reflog 用来记录你的每一次命令(只有reset和commit [ 即:对版本库的操作 ]):

git reflog
ff8a5d3 HEAD@{1}: reset: moving to HEAD^
d0e121c HEAD@{2}: reset: moving to HEAD^
d696468 (HEAD -> master) HEAD@{3}: commit: read boke to change
d0e121c HEAD@{4}: commit: add four line
ff8a5d3 HEAD@{5}: reset: moving to ff8a5d
c471641 HEAD@{6}: reset: moving to HEAD~2
ff8a5d3 HEAD@{7}: commit: add three line
5222f9d HEAD@{8}: commit: add one line
c471641 HEAD@{9}: commit (initial): 2020 6 29

幸运的是,你的commit被记录了,所以你可以看见你最新版本的版本号:d696468。

所以可以使用:

git reset --hard d69646(版本号)

注意:版本号,不要写太少,不然可能有重复,而使git无法确定具体是哪一个版本!

理解工作区和暂存区

Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念

工作区(Working Directory):

工作区 就是你在电脑里能看到的目录就是一个工作区:

image.png

暂存区(stage)

工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库(Repository)

Git的版本库里存了很多东西,其中最重要的就是称为stage的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。

在这里插入图片描述

前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:

  • 第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区
  • 第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支

你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。(这里就是为什么可以add多个文件后,一次性commit,下面 为什么Git比其他版本控制系统设计得优秀 --> 继续理解暂存区 有讲!)

还是实践出真理,大家可以修改一下自己的文件,然后再新建一个文本文件到工作区(暂时不用提交到git仓库),然后运行 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

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        LICENSE

no changes added to commit (use "git add" and/or "git commit -a")

Git非常清楚地告诉我们,readme.txt 被修改了(Changes not staged),而 LICENSE 还从来没有被添加过,所以它的状态是 Untracked

现在,使用两次命令 git add ,把 readme.txt 和 LICENSE 都添加后,用 git status 再查看一下:

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   readme.txt
        new file:   LICENSE

如果报错

warning: LF will be replaced by CRLF in test1.txt. The file will have its original

可以不管,但是菜鸟不能让问题溜走,解决办法

现在,暂存区的状态就变成这样了:

在这里插入图片描述

所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支

一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是 “干净” 的:

在这里插入图片描述

为什么Git比其他版本控制系统设计得优秀 --> 继续理解暂存区

为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件

我们还是做实验。第一步,对 readme.txt 做一个修改,比如:加一行内容,然后添加到暂存区,然后再次修改readme.txt ,最后提交到git仓库。提交后,再用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 add -> 第二次修改 -> git commit

我们前面讲了,Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以 git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

提交后,用git diff命令可以查看工作区和版本库里面最新版本的区别:

warning: LF will be replaced by CRLF in gittwo.
The file will have its original line endings in your working directory
diff --git a/readme.txt b/readme.txt
index da64460..ab83e4f 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,3 +1,3 @@
 this is two git!
 try change to see status!
-try xiugai!
+try guanlixiugai!

可见,第二次修改确实没有被提交。(读者修改肯定跟我不一样,所以以自己为准)

那怎么提交第二次修改呢?你可以继续git add再git commit,也可以别着急提交第一次修改,先git add第二次修改,再git commit,就相当于把两次修改合并后一块提交了:

第一次修改 -> git add -> 第二次修改 -> git add -> git commit

看到这里,相信你一定理解了暂存区!

理解分支与分叉

见:git自我研究:分支与分叉

创建和切换分支

git branch 和 git switch

git checkout -b dev

git switch -c dev

git branch dev
git checkout dev

git branch dev
git switch dev

注:

git checkout 这个命令承担了太多职责,既被用来切换分支,又被用来恢复工作区文件,对用户造成了很大的认知负担。

Git社区发布了Git的新版本2.23。在该版本中,有一个特性非常引人瞩目,就是新版本的Git引入了两个新命令 git switch 和 git restore,用以替代现在的 git checkout。

git switch 到 tag 上

git switch 还能直接跳转到 tag 上,eg:

git checkout v1.0

或者

git switch v1.0

可能会提示

If you want to detach HEAD at the commit, try again with the --detach option.

这个时候只能使用:

git checkout --detach v1.0

注:

出现这个提示一般是因为tag所在的分支被删除了!

git remote

要查看远程库的信息,用git remote -v 显示更详细的信息:

  1. https
$ git remote -v
origin  https://github.com/pbw-langwang/gitskills.git (fetch)
origin  https://github.com/pbw-langwang/gitskills.git (push)
  1. ssh
$ git remote -v
origin  git@github.com:pbw-langwang/gitskills.git (fetch)
origin  git@github.com:pbw-langwang/gitskills.git (push)

上面显示了可以抓取(fetch)和推送(push)的origin的地址。如果没有推送权限,就看不到push的地址。

git log

git log 其实有很多参数,且不同参数能看见的东西不一样,这里菜鸟推荐一个命令(来自廖雪峰大佬):

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

其效果:

$ git lg
*   =format:61fea98 - (HEAD -> master, origin/dev2) Merge branch 'dev2' of github.com:pbw-langwang/gitskills (22 hours ago) <*>
|\
| * =format:1cf6ec6 - add dev2 (23 hours ago) <*>
* | =format:5d06e73 - (origin/master, origin/HEAD) change b.txt (25 hours ago) <*>
* |   =format:8a5cb29 - fix master and dev (26 hours ago) <*>
|\ \
| * \   =format:b144529 - (origin/dev) fix env conflict (27 hours ago) <*>
| |\ \
| | * | =format:13c0bba - add env (27 hours ago) <*>
| * | | =format:6b07599 - b aaddenv (27 hours ago) <*>
| |/ /
| * / =format:e0e5529 - add dev (28 hours ago) <*>
| |/
| * =format:180d386 - add b (29 hours ago) <*>
|/
* =format:036b30f - Initial commit (9 days ago) <*>

想退出直接按q就行!

.gitignore 文件注意事项

1..gitignore只能忽略那些原来没有被纳入了版本管理中的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。加入.gitignore文件时一定要先从staged移除,才可以忽略。

  • 修改 .gitignore 文件来列出需要忽略的文件

  • 使用 git rm --cached <file> 将已经被 Git 跟踪的文件从版本控制中移除,但保留本地文件

2.在使用.gitignore文件后如何删除远程仓库中以前上传的此类文件而保留本地文件?

按照1中的方式即可,然后进行commitpush,这样会发现远程仓库中的不必要文件就被删除了,以后可以直接使用git add -A来添加修改的内容,上传的文件就会受到.gitignore文件的约束。

强制添加文件

但是有时候又会出现,你添加到了.gitignore文件的文件,你又想加入进去,最简单的办法是修改.gitignore文件,如果你不想改动,就可以通过下面的办法进行添加!

有些时候,你想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了:

$ git add gitig2.txt
The following paths are ignored by one of your .gitignore files:
gitig2.txt
hint: Use -f if you really want to add them.
hint: Turn this message off by running
hint: "git config advice.addIgnoredFile false"

如果你确实想添加该文件,可以用 -f 强制添加到Git

$ git add -f gitig.txt

通过git status查看提交状况:

$ git status
On branch dev
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   gitig.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:   dev.txt

那么如果撤回会怎么样?

$ git restore --staged gitig.txt

$ git status
On branch dev
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:   dev.txt

no changes added to commit (use "git add" and/or "git commit -a")

发现gitig.txt再次被忽略了!

查找哪里规则出错

或者你发现.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:

$ git check-ignore -v gitig.txt
.gitignore:1:*.txt      gitig.txt

Git会告诉我们,.gitignore的第1行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。

GitHub远程仓库

GitHub远程仓库,这里读者自己注册GitHub账号

GitHub添加ssh

第一步:创建SSH Key。在用户主目录下(一般是:c://Users/asus/ 或者 自己全盘搜一下),看看有没有.ssh目录,如果有,再看看这个目录下有没有 id_rsaid_rsa.pub 这两个文件,如果已经有了,可直接跳到下一步。如果没有,在git仓库处打开Git Bash,创建SSH Key:

ssh-keygen -t rsa -C "youremail@example.com"

需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码 (Enter passphrase (empty for no passphrase): 不需要打密码) 。

第二步:打开GitHub

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

添加title,随便什么都可以。

填写key,自己的.ssh里面的id_rsa.pub文件的内容。

为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以GitHub只有知道了你的公钥,就可以确认只有你自己才能推送。 --> 一般推荐https

GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

GitHub创建仓库

在这里插入图片描述

在这里插入图片描述

Repository name按照自己的git仓库起名就好,其它默认(不要选创建readme文件),然后确定。

在这里插入图片描述

目前,在GitHub上的这个Git_warehouse仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后把本地仓库的内容推送到GitHub仓库。

关联仓库

现在,我们根据GitHub的提示,在本地的Git_warehouse仓库下运行命令:

git remote add origin https://github.com/pbw-langwang/Git_warehouse.git  (推荐)
git remote add origin git@github.com:pbw-langwang/Git_warehouse.git

注意:把上面的pbw-langwang替换成你自己的GitHub账户名,否则你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH Key公钥不在我的账户列表中。

添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的。

然后执行:

git push -u origin master

出现输入GitHub用户名和密码界面 (https是这样,ssh菜鸟没试,如果有警告可以看看下面的SSH警告)

把本地库的内容推送到远程,用 git push 命令,实际上是把当前分支master推送到远程。由于远程库是空的,我们第一次推送master分支时,加上了 -u 参数,Git不仅会把本地的 master 分支内容推送的远程新的master 分支,还会把本地的 master 分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

刷新GitHub页面,看到远程库的内容已经和本地一模一样:

在这里插入图片描述

从现在起,只要本地作了提交,就可以通过命令:

git push origin master

把本地master分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!

SSH警告

当你第一次使用Git的clone或者push命令连接GitHub时,会得到一个警告:

The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?

这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes回车即可。(如果你是用的https,则不会警告这个)

Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:

Warning: Permanently added 'github.com' (RSA) to the list of known hosts

这个警告只会出现一次,后面的操作就不会有任何警告了。

如果你实在担心有人冒充GitHub服务器,输入yes前可以对照GitHub的RSA Key的指纹信息是否与SSH连接给出的一致。

小结

  1. 要关联一个远程库,使用命令 git remote add origin https://github.com/pbw-langwang/Git_warehouse.git

  2. 关联后,使用命令 git push -u origin master 第一次推送master分支的所有内容

  3. 此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改

  4. 一个工作区可以关联好几个远程仓库

  5. 分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步