跳到主要内容

Git常用操作

这是 2017 年做的笔记,从 2018 年开始用 VSCode 开发,VSCode 与 Git 良好的集成,使得许多功能只需要简单地点点按钮就可以实现,不过这篇笔记还是保留着吧,说不定什么时候还能用上。

提交本地更改

徐老师周五中午加餐,给大家讲解 Git 基本操作的涵义。借着这个机会,我也来说一下我对 git addgit commit 的理解吧。

git add

这个命令存在的意义,是让我可以只提交(commit)一部分文件。为什么会有这样的需求?

举个例子,我想写个网站,那么一般都是一次写一个小功能,写完了再写下一个小功能。

假如我今天时间有限,但是客户又想看我完成了哪些工作。

注册功能我写完了,是文件 a.js;登录功能还没写完,是文件 b.js

那么我就可以用 git add a.js,只将文件 a.js 加入暂存区。

然后执行 git commit -m "update",将这个文件提交至本地仓库。

再用 git push origin master,将修改推送至服务器。

这样,客户就能够在网上看到我今天完成的注册功能了。

参考资料:

git commit

Git 的一大功能,就是可以将文件退回到某次 commit 之前的版本,也就是笑来老师说的时光机。

这个功能要怎么用呢?我们继续用上面的例子。

假设在编写注册功能的时候,我 commit 了两次:第一次只完成了基础的注册功能,第二次增加了对用户密码强度的检验。

但是客户在试用注册功能的时候,发现检验密码强度的功能有问题,需要暂时取消密码强度的检验,这个时候应该怎么做呢?

我就可以用 git reset 或者 git revert 命令,来撤销最近一次的 commit,并执行 git push。这样,网站就只有基础的注册功能,没有密码强度检验功能了。

参考资料:

撤销本地更改

在不同的分支上做不同的工作,切换来切换去难免出错。即使只在一个分支上工作,也有可能犯错。有时候想要把本地 尚未提交的更改 撤销了,应该怎么做呢?跟我来。

本地的更改,分为三种类型:

  1. 新建文件且未加入暂存区。
  2. 修改现有文件且未加入暂存区。
  3. 修改现有文件且已加入暂存区。

下面就分别讲一下这三种情况应当如何撤销更改。

新建文件且未加入暂存区

git clean -f

这个命令,会把所有新建且未加入暂存区的文件都删除(不包含文件夹),将项目还原到新建文件之前的状态。

git clean -f -n

加上 -n 命令,不会执行实际的删除操作,而是将执行 git clean -f 命令之后会删除的所有文件都列出来,让用户查看,以免误删文件。

git clean -f -d

加上 -d 命令,会把新建的文件和文件夹都删除。

修改现有文件且未加入暂存区

这个也很简单,一条命令就搞定。

> git checkout -- .
// 或者
> git checkout topics/git.md

注意,第二条命令中,对于需要撤销更改的文件,要输入其相对于当前路径的相对路径。

比如我要撤销根目录下的 topics/git.md 文件中的更改,如果当前路径在根目录,那么就执行 git checkout topics/git.md;而如果当前路径在 ~/topics 中的话,就应该执行 git checkout git.md

修改现有文件且已加入暂存区

git reset --hard

注意,这条命令所撤销的修改,不仅包括已加入暂存区的现有文件,也包括尚未加入暂存区的现有文件。

换句话说就是,只要是对现有文件进行了修改,这条命令就都会删除!

参考资料:

和老师的仓库保持同步

首次同步老师仓库的更改至本地

因为自己和老师的仓库都是一直在更新,所以每次提交前,需要先将自己的仓库(包括远程和本地)同步为老师仓库的最新状态,然后再提交自己的更改。

首先,添加老师的仓库,并命名为 upstream,方便以后再次调用。

> git remote add upstream https://github.com/xugy0926/getting-started-with-javascript.git

然后将老师仓库中的内容下载至本地。注意,下载至本地的老师的远程仓库内容,和自己的仓库是互不干扰的。

> git fetch upstream master

这时,徐老师仓库的最新内容已经下载至本地了。

我们先不急着合并进来,先看看自己的仓库和老师的仓库有哪些不同。

> git diff master upstream/master

下图是执行后的结果。

git-diff-result

注意,执行 git diff 后,终端会进入 Vim 环境,注意看图中最后一行行首的冒号。按一下 q 键,退出 Vim 环境。

对比完徐老师和自己仓库的不同之后,就要把这些内容(upstream/master,代表 upstream 这个源的 master 分支)合并到自己仓库的当前分支中了。

> git merge upstream/master

然后就可以提交自己的更改,或者进行其它操作了。

从老师的仓库持续更新

对比本地仓库某分支和远程仓库某分支

每次准备提交自己的改动之前,先检查一下老师的仓库有没有更新是个好习惯。那么怎样能够知道老师的仓库有没有更新呢?

一条命令就够了。

> git diff upstream/master patch
<!-- 比较老师的远程仓库和本地的 patch 分支有什么不同 -->
diff --git a/.gitignore b/.gitignore
index 039c699..45e4005 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,3 @@
package-lock.json
node_modules
dist
-.vscode
\ No newline at end of file
diff --git a/homework/lesson3/fangqingyang.jpg b/homework/lesson3/fangqingyang.jpg
deleted file mode 100644
index edcfdca..0000000
Binary files a/homework/lesson3/fangqingyang.jpg and /dev/null differ
diff --git a/homework/lesson3/fangqingyang.md b/homework/lesson3/fangqingyang.md
deleted file mode 100644
index 78d0b6d..0000000
--- a/homework/lesson3/fangqingyang.md
+++ /dev/null
@@ -1,12 +0,0 @@
-## 第三节课总结
-
-函数是JS里面地位最高的语法,它也可以是对象。有了函数就可以模块化,就像搞大扫除
:

从上面的代码可以看出来,本地仓库和老师远程的仓库是不同的,那就得把本地的仓库更新了。

那如果本地仓库和老师仓库是一样新的,执行上面的命令会是什么结果呢?

> git checkout note
> git diff upstream/master note
(END)

看,如果和老师的仓库没有区别的话,执行 git diff 之后,只会显示 (END),说明两者没有差异,就不用更新了。

对于需要更新的情况,有两种方式可以将远程仓库的更新合并至本地仓库。

将远程仓库 merge 进本地仓库(推荐)

用这个方法,有可能需要处理出现的 merge 信息:Merge remote-tracking branch 'upstream/master'

> git fetch upstream master

出现这个信息,是因为在上次提交之后,本地仓库和远程仓库各自有一些更新。而在将远程仓库合并至本地时,就需要有这样的 merge 信息,来说明这一操作。

将远程仓库 rebase 并 merge 进本地仓(不推荐)

> git rebase -i upstream/master
<!-- 进入 vim 编辑器处理 commits,完成后保存退出 -->
> git merge upstream/master

上面的两行代码,先编辑本地仓库上的提交信息;在编辑完成后,将最后一次和远程仓库同步之后,所做的更改,全都加到远程仓库最新更改的前面,这样就会让整个更改历史看起来足够明确。

参考资料:

善用分支功能

新建功能分支,各自完成不同的需求

因为自己一边要做笔记,一边还想向徐老师的项目上提 PR。由于笔记还没写完,但是笔记已经有过多次 commits 了,所以在提 PR 的时候,不想把自己的笔记提交上去。

虽然可以通过 git revert或者 git reset 来撤销 commit,但是自己还想保持 commit 历史的完整性。

这个时候,就可以在自己的项目中新建一个分支 work 用来提交 PR,默认的分支 master 则用来做笔记,两边互不干扰,又能同时完成两项任务。

首先,将老师的仓库的最新版拉到本地(添加 upstream 源的操作请查看前面的内容,此处不再重复)。

> git fetch upstream master

用拉到本地的仓库新建一个隶属于自己项目的分支 work

> git checkout -b work upstream/master

接着将 91d4fce 这次 commit 的内容(修改老师文章中文字错误和格式的操作)放入新增的 work 分支中。

> git cherry-pick 91d4fce

将本地分支 work 推送至服务器上,并指定 origin 为默认主机。完成之后,在网页端提交自己的 PR 即可。

> git push -u origin work

等到暂时没有 PR 需要提交了,并且自己的作业也写完的时候,就可以将先将 work 分支与徐老师最新的代码同步,然后再将 work 分支同步到 master 分支上就行了。

参考资料:

同步某分支至本地

因为上面新建的 work 分支只在办公室的台式机上有,家里的笔记本上并没有这个分支。为了在家里也能够继续学习,就需要把这个分支同步到家里的电脑上。

最开始是用关键字 git clone branch 来查找方法的,试了一下之后发现这个关键字搜到的解决方法,只是把远程分支的文件下载到本机,本机并没有之前所建立的 work 分支,所以需要更换搜索关键字。

想到可以用 git pull 将代码更新至本地,尝试了一下,发现本地并没有将 work 分支同步下来。

于是又用 how to pull new branch from remote 作为关键字来 Google,参照着这篇教程 《Syncing》 操作,终于成功了。

先是将 work 分支拉至家里的笔记本上,这时候 work 还没有合并至本机的仓库中。这样可以避免操作错误,影响本机的仓库。

> git fetch origin work

接着再将 work 分支合并至本机的仓库之中。

> git merge origin/work

最后再切换一下分支,就可以在家里的笔记本上继续快乐地学习了~

> git checkout work

参考资料:

删除无用分支

有时候,只是想新建一个分支,测试一下某个功能。测试完成之后,这个分支就用不到了,那就需要把这个分支删掉。

在 Google 中用关键字 git remove branch 搜一下,看到 StackOverflow 上就有一篇文章,很完整地列出了删除分支的方法:How do I delete a Git branch both locally and remotely?

同时删除本地和远程分支

git push -d 命令用于删除远程分支,git branch -d 命令则用于删除本地分支。

> git push -d origin test
> git branch -d test

强制删除本地分支

有时候会提示本地分支无法删除,如果确定要执行删除操作的话,那我们就来个强制执行。

注意这里的 -D 是大写,编程输入代码时,大小写一定要看仔细。

> git branch -D work

参考资料:

重命名分支并在其它机器上同步

在学习的过程中,觉得目前分支的使用方式还是不太好。于是打算改成在 master 分支上进行各种测试,work 分支重命名为 note 分支,专门用来提交每一节课的笔记。另外新建一个 patch 分支,用于修改老师仓库里文章中的错字、格式等等。

新建分支的方式前面已经说过了,那么重命名分支的需求应该如何实现呢?

git rename branch 为关键字进行搜索,看到第一个答案是 How do I rename a local Git branch?,在问题里又提到了 Rename master branch for both local and remote Git repositories 这个回答,把最高票回答看了一遍,照着这个方法试试,先在办公室的台式机上进行测试。

重命名本地分支

首先,重命名本地的 work 分支为 note

> git branch -m work note

操作完成后,查看本地的分支列表,确认一下。

> git branch --list
* master
patch
note

上面是输出结果,可以看到本地分支里没有 work了,并且有 note,说明本地分支更新成功了。

删除远程分支

然后删除远程的 work 分支,结果报错。

> git push remote :work
fatal: 'remote' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

看来这个方法不行,那就再看看刚才那个链接里其他人的回答,往下翻,第二个回答和第一个差不多,第三个有些不一样,把上面命令中的 remote 换成了 origin,来,试一试。

> git push origin :work

第一次执行失败,提示超时错误,说明输入的命令是对的,只是网络状态不太好。

fatal: unable to access 'https://github.com/Dream4ever/getting-started-with-javascript.git/': Failed to connect to github.com port 443: Timed out

再执行一次,这下成功了。

To https://github.com/Dream4ever/getting-started-with-javascript.git
- [deleted] work

更新对应关系

这个时候,本地的分支由 work 变成了 note,远程分支 work 被删除了,那么本地的 note 分支对应远程的哪个分支呢?

我们先看看远程有哪些分支。

> git branch -r
origin/HEAD -> origin/master
origin/master
origin/patch
upstream/master

嗯,果然没有 work 分支了,note 分支还没有被推送,自然也没有。

接着再看看本地分支和远程分支之间的对应关系。

> git branch -vv
* master 79fb746 [origin/master] Merge remote-tracking branch 'upstream/master'
patch 9320211 [origin/patch] Merge pull request #81 from WangZhong2014/master
note 9320211 [origin/work: gone] Merge pull request #81 from WangZhong2014/master

从上面的输出结果里可以看到,本地和远程的 master 分支和 patch 分支是正常的对应关系。但是本地的 note 分支理应对应着远程的 work 分支,而远程的 work 分支又被删除了。所以,就需要把本地的 note 分支上传到远程,并让本地和远程的 note 分支相互对应。

我们先在本地切换至 note 分支。

> git checkout note
Switched to branch 'note'
Your branch is based on 'origin/work', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)

从上面的执行结果可以看出来,Git 提示本地的 note 分支是基于 origin/work 分支,但是远程的 work 分支已经不存在了,可以用 git branch --set-upstream 命令修复。但是我们是要将本地的 note 分支上传到远程并将两者关联,所以需要再执行下面的命令。

> git push --set-upstream origin note
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/Dream4ever/getting-started-with-javascript.git
* [new branch] note -> note
Branch note set up to track remote branch note from origin.

这时候再查看一下本地和远程分支的对应关系。

  master 79fb746 [origin/master] Merge remote-tracking branch 'upstream/master'
patch 9320211 [origin/patch] Merge pull request #81 from WangZhong2014/master
* note 9320211 [origin/note] Merge pull request #81 from WangZhong2014/master

好了,这下在办公室的台式机上完成了分支的重命名工作。接下来,就要在家里的笔记本上更新分支。

在另一台电脑上同步分支

首先,要清除另一台电脑上无效的对应关系。

> git fetch -p
From https://github.com/Dream4ever/getting-started-with-javascript
x [deleted] (none) -> origin/work

然后,把新的 note 分支同步下来就可以了。

> git fetch origin note
From https://github.com/Dream4ever/getting-started-with-javascript
* branch note -> FETCH_HEAD
> git checkout -b note origin/note
Branch note set up to track remote branch note from origin.
Switched to a new branch 'note'

好了,终于大功告成了!哈哈哈哈。

参考资料:

用一个分支完全覆盖另一个分支

下面的命令,将用 note 分支完全覆盖 master 分支。

> git checkout master
> git tag old-master-branch
> git reset --hard note
> git merge -m 'Merge -s our where _ours_ is the branch note' -s ours origin/note
> git push -f origin master

参考资料:

修改历史提交

只修改提交信息(commit message)

先参考 How do you make changes on a specific commit 这篇文章中的方法,修改需要变更的历史提交信息。但是修改之后,提交时间就变了。

所以还要参考 git rebase without changing commit timestamps 这里的回答,将提交时间改为文件修改时间。

其它配置

让 Git 在终端里显示中文

对于非 ASCII 字符,Git 默认是以八进制形式显示的。

<!-- 八进制形式 -->
...270\215\346\224\276\345\274\203 lesson5 Wei.md"

通过执行下面的命令,可以让 Git 在终端中显示非 ASCII 原本的样子。

>  git config --global core.quotepath off
<!-- 可以正常显示了 -->
.../lesson5/小白JS入门不放弃 lesson5 Wei.md

参考资料:

让 Git 不再总是提示输入用户名和密码

方法其实很简单,将项目的 .git/config 文件,由下面的这种形式:

[remote "origin"]
url = https://github.com/Dream4ever/Anniversary.git

改为下面这种形式:

[remote "origin"]
url = git@github.com:Dream4ever/JavaScript.git

以指定的时间提交变更

最简单的方法:

git commit --date="1 day ago" -m "commit message"

参考链接:How do I make a Git commit in the past?

不再跟踪某个文件新的变更

某个文件之前的变更一直在用 git 跟踪,但是之后的变更不想再被跟踪了,比如保存关键信息的配置文件,之前随便填了一些内容,现在填的是实际的数据,就需要实现这种需求。这种要求,一条命令就可以搞定:

git update-index --skip-worktree <file>

如果要撤销该操作,恢复对该文件变更的跟踪,也很简单:

git update-index --no-skip-worktree <file>

参考链接:

通过本机将云服务器上的更改同步至 GitHub

由于云服务器在国内,访问 GitHub 的话还得配置代理,这样不安全。

可以在本机先配置好云服务器的 SSH 访问环境,比如在配置文件 c:\Users\You\.ssh\config 中加上下面这一段:

Host ecs
HostName 1.2.3.4
Port 22
User You
IdentityFile ~/.ssh/ecs.pem

这样就可以在命令行通过 ssh ecs 来连接云服务器了。

然后在本地的 repo 中添加一个远程仓库,指向云服务器上的 repo。

git remote add ecs ssh://ecs:/D:/website/path/of/repo

这样就可以在本地通过 git push ecs master 来将本地的更改推送至云服务器了,也可以通过 git push origin master 将本地的更改推送到 GitHub,就不需要在云服务器上配置代理了。