1. 1. 克隆仓库:
  2. 2. 未暂存的内容
    1. 2.1. 把未暂存的内容添加到暂存区
    2. 2.2. 把未暂存的内容移动到一个新分支
    3. 2.3. 把未暂存的内容移动到另一个已存在的分支
    4. 2.4. 放弃未暂存的修改
  3. 3. 暂存的内容
    1. 3.1. 把暂存的内容添加到上一次的提交
    2. 3.2. 取消暂存的内容
  4. 4. 编辑提交
    1. 4.1. 修改提交信息
    2. 4.2. 修改提交里的用户名和邮箱
    3. 4.3. 从一个提交(commit)里移除一个文件
    4. 4.4. 撤销某一次提交
      1. 4.4.1. 修改已经提交到了当前分支,但是还没有 push 到远程仓库
      2. 4.4.2. 已经 push 到远程仓库的提交
      3. 4.4.3. revert 与 reset 的区别:
      4. 4.4.4. revert Merge Commit
    5. 4.5. 意外的做了一次硬重置(hard reset),如何找回内容
    6. 4.6. 查看 commit 历史
    7. 4.7. 查看某次提交
  5. 5. Stash
    1. 5.1. 暂存工作目录下的所有改动
    2. 5.2. 暂存所有改动,包括 untracked 的文件(新建的文件)
    3. 5.3. 暂存指定文件
    4. 5.4. 暂存时记录消息
    5. 5.5. 使用某个指定暂存
    6. 5.6. 使用最后一个 stash 的状态,并删除这个 stash
    7. 5.7. 删除所有的 stash
    8. 5.8. 仅从 stash 中拿出某个文件的修改
  6. 6. 比较差异
  7. 7. 分支
    1. 7.1. 需要提交到一个新分支,但错误的提交到了 master
    2. 7.2. 从错误的分支拉取了内容,或把内容拉取到了错误的分支
    3. 7.3. 恢复误删除的分支。
  8. 8. 标签
    1. 8.1. 恢复已删除标签
  9. 9. 远程仓库
    1. 9.1. 同步远程仓库到自己 fork 的仓库
  10. 10. Rebase 和 Merge
    1. 10.1. rebase 和 merge 有什么区别
      1. 10.1.1. rebase
      2. 10.1.2. merge
    2. 10.2. 撤销 rebase/merge
  11. 11. 配置
    1. 11.1. 配置 http 和 socks 代理
    2. 11.2. 配置常用的命令别名
    3. 11.3. 配置 git log 格式
    4. 11.4. 生成压缩包

Git 的一些使用技巧

Git 是一个非常强大的版本控制工具,是程序员必须要掌握的技能。

这里记录常用的命令技巧和碰到的一些问题。

首先应该了解 Git 里面的几个概念。

  • Workspace:工作区
  • Index/Stage:暂存区(使用 git add 命令将工作区的改动添加到暂存区)
  • Repository:本地仓库(使用 git commit 命令将暂存区的改动提交到本地仓库)
  • HEAD:指向本地仓库的当前版本,上一个版本就是 HEAD^,上上一个版本就是 HEAD^^,往上 10 个版本,可以
    写成 HEAD~10
  • Remote:远程仓库

克隆仓库:

1
git clone git@github.com:shipengqi/shipengqi.github.io.git <target dir> -b <branch>

shipengqi.github.io.git 克隆到 target dir 指定的文件夹(默认是远程仓库的名字),并切换到指定
分支 branch(默认是 master 分支)。

未暂存的内容

把未暂存的内容添加到暂存区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 提交所有改动
git add -A

# 提交被修改 (modified) 和被删除 (deleted) 文件,不包括新文件 (new)
git add -u

# 提交新文件 (new) 和被修改 (modified) 文件,不包括被删除 (deleted) 文件
git add .

# 添加指定文件到暂存区
git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
git add [dir]

# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
git add -p

把未暂存的内容移动到一个新分支

1
git checkout -b new-branch

把未暂存的内容移动到另一个已存在的分支

1
2
3
git stash
git checkout my-branch
git stash pop

放弃未暂存的修改

1
2
3
4
git checkout <file-name>

# 放弃所有修改
git checkout .

暂存的内容

把暂存的内容添加到上一次的提交

1
git commit --amend

取消暂存的内容

添加到暂存区的文件,但是还没有提交,如果想要撤销暂存的文件,可以使用 git reset HEAD <file1> <file2>... 的方式取消暂存。

1
2
3
4
5
6
7
git reset HEAD file2
# 或者
# 同时删除工作区和暂存区中的文件
git rm [file1] [file2] ...

# 从暂存区删除文件, 但工作区不删除
git rm --cached [file]

这样 file2 文件又回到了之前已修改未暂存的状态。

编辑提交

修改提交信息

1
2
3
4
5
# 打开默认编辑器
git commit --amend --only

# 或者
git commit --amend --only -m 'xxxxxxx'

如果已经 push 了这次提交, 那么可以修改这次提交(commit)然后强推(force push), 但是不推荐。

修改提交里的用户名和邮箱

1
git commit --amend --author "New Authorname <work@example.com>"

从一个提交(commit)里移除一个文件

从一个提交(commit)里移除一个文件:

1
2
3
git checkout HEAD^ myfile
git add -A
git commit --amend

当你有一个开放的补丁(open patch),你往上面提交了一个不必要的文件,需要强推(force push)去更新这个远程补丁。

撤销某一次提交

修改已经提交到了当前分支,但是还没有 push 到远程仓库

1
2
3
4
5
6
7
8
git reset --hard HEAD^

# 回退到指定的版本
git reset --hard <commit id>

# 只撤销提交,但不改变代码
git reset HEAD^
git reset commit-id

已经 push 到远程仓库的提交

已经 push 到远程仓库的提交,使用 git revert,回滚到指定的历史版本,再 git push 更新远程仓库。

1
2
# 撤销某个 commit 版本
git revert commit-id

对于已经 push 到远程仓库的提交,也可以使用 reset,然后执行 git push -f 强制推到远程仓库中去,但是可能导致冲突。
如果只是回退到上一个 commit,建议使用 revert,如果要回退到多个版本之前,还是要使用 reset。

revert 与 reset 的区别:

  • reset 是在正常的 commit 历史中,删除了指定的 commit,这时 HEAD 是向后移动了,而 revert 是在正常的 commit 历史中再 commit 一
    次,HEAD 是一直向前的。
  • 对于已经把代码已经 push 到远程仓库,reset 删除指定 commit 以后,push 可能导致一大堆冲突.但是 revert 不会。
  • revert 是撤销指定的某个 commit 版本,但是指定 commit 之后的版本,还会保留下来。reset 是将 HEAD 移动到了指定的 commit,
    指定 commit 之后的版本都会被丢弃。

如果想恢复到之前某个提交的版本,且那个版本之后提交的版本都不要了,就用 reset。
如果想撤销之前的某一版本,但是又想保留该目标版本后面的版本,就用 revert。

revert Merge Commit

执行 git revert commitId 可能会报错:

1
error: commit xxxxxxxxxxx is a merge but no -m option was given.

这是因为指定的 commit 是一次 merge,需要 -m 参数指定要 revert 的这个 merge commit 中的哪一个。
比如:git revert HEAD~1 -m 1 会 revert 第一个 commit。

你也可以在 git log 找到你要 revert 的那个 commit。

意外的做了一次硬重置(hard reset),如何找回内容

当你用 git reset --hard HEAD^ 回退到上个版本时,再想恢复,就必须找到要恢复版本的 commit id。可以通过 git reflog 找到
那次 commit。

选择你想要回到的提交(commit)的 commit id,再重置一次:

1
git reset --hard SHA1234

查看 commit 历史

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 显示当前分支的版本历史
git log

# 显示 commit 历史,以及每次 commit 发生变更的文件
git log --stat

# 搜索提交历史,根据关键词
git log -S [keyword]

# 显示某个 commit 之后的所有变动,每个 commit 占据一行
git log [tag] HEAD --pretty=format:%s

# 显示某个 commit 之后的所有变动,其"提交说明"必须符合搜索条件
git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名
git log --follow [file]
git whatchanged [file]

# 显示指定文件相关的每一次 diff
git log -p [file]

# 显示过去 5 次提交
git log -5 --pretty --oneline

# 显示所有提交过的用户,按提交次数排序
git shortlog -sn

# 显示指定文件是什么人在什么时间修改过
git blame [file]

# 查看命令历史
git reflog

回退前,用 git log 可以查看提交历史,以便确定要回退到哪个版本。
回退后,用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。

查看某次提交

1
2
3
4
5
6
7
8
# 显示某次提交的元数据和内容变化
git show [commit]

# 显示某次提交发生变化的文件
git show --name-only [commit]

# 显示某次提交时,某个文件的内容
git show [commit]:[filename]

Stash

stashadd 的区别:
git stash 的作用是把工作区(必须是工作区中已经被 git 追踪到的文件)和暂存区中的内容暂时存到一个栈上。而且这个堆是和分支不
相关的。切换分支后,依然可以看到并使用。

git add 命令将修改添加到暂存区。

暂存工作目录下的所有改动

1
git stash

暂存所有改动,包括 untracked 的文件(新建的文件)

1
git stash -u

暂存指定文件

1
2
3
4
5
# 暂存某一个文件
git stash push working-directory-path/filename.ext

# 暂存多个文件
git stash push working-directory-path/filename1.ext working-directory-path/filename2.ext

暂存时记录消息

1
2
3
4
git stash save <message>

# 或
git stash push -m <message>

使用某个指定暂存

1
2
3
4
5
# 查看 stash 记录
git stash list

# apply 某个 stash
git stash apply "stash@{n}"

n 是 stash 在栈中的位置,最上层的 stash 会是 0

使用最后一个 stash 的状态,并删除这个 stash

1
git stash pop

删除所有的 stash

1
git stash clear

仅从 stash 中拿出某个文件的修改

1
git checkout <stash@{n}> -- <file-path>

比较差异

1
2
3
4
5
6
7
8
9
10
11
# 显示暂存区和工作区的差异
git diff

# 显示本地仓库中任意两个 commit 之间的文件变动
git diff <commit-id> <commit-id>

# 显示暂存区和最近的 commit 的不同
git diff --cached

# 显示工作区与当前分支最新 commit 之间的差异
git diff HEAD

分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 切换分支
git checkout dev

# 切换并新建一个分支
git checkout -b newBranch

# 删除一个分支
git branch -d branch

# 列出所有本地分支
git branch

# 列出所有远程分支
git branch -r

# 列出所有本地分支和远程分支
git branch -a

# 删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]

# 合并指定分支到当前分支
git merge [branch]

# 选择一个 commit,合并进当前分支
git cherry-pick [commit]

# 关联远程分支
git branch -u origin/mybranch
# 或者在 push 时加上 -u 参数
git push origin/mybranch -u

# 重命名本地分支
git branch -m <new-branch-name>

需要提交到一个新分支,但错误的提交到了 master

在 master 下创建一个新分支,不切换到新分支,仍在 master 下:

1
(master)$ git branch my-branch

把 master 分支重置到前一个提交:

1
(master)$ git reset --hard HEAD^

checkout 到刚才新建的分支继续工作:

1
(master)$ git checkout my-branch

从错误的分支拉取了内容,或把内容拉取到了错误的分支

使用 git reflog 找到在这次 pull 之前 HEAD 的指向。

1
2
3
(master)$ git reflog
ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here

重置分支到你所需的提交:

1
git reset --hard c5bc55a

恢复误删除的分支。

有些时候可能删除了还没有推到远程的分支,如何恢复?例,创建一个分支,并做一次提交:

1
2
3
4
5
6
7
8
9
10
(master)$ git checkout -b my-branch
(my-branch)$ git branch
(my-branch)$ touch foo.txt
(my-branch)$ ls
README.md foo.txt
(my-branch)$ git add .
(my-branch)$ git commit -m 'foo.txt added'
(my-branch)$ foo.txt added
1 files changed, 1 insertions(+)
create mode 100644 foo.txt

现在我们切回到主(master)分支,‘不小心的’删除 my-branch 分支

1
2
3
4
5
6
7
(my-branch)$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
(master)$ git branch -D my-branch
Deleted branch my-branch (was 4e3cd85).
(master)$ echo oh noes, deleted my branch!
oh noes, deleted my branch!

开始恢复删除的分支,先使用 reflog 命令, 它存储了仓库(repo)里面所有动作的历史。

1
2
3
4
(master)$ git reflog
69204cd HEAD@{0}: checkout: moving from my-branch to master
4e3cd85 HEAD@{1}: commit: foo.txt added
69204cd HEAD@{2}: checkout: moving from master to my-branch

可以看到一个删除分支的提交 hash(commit hash),开始恢复:

1
2
3
4
5
6
(master)$ git checkout -b my-branch-help
Switched to a new branch 'my-branch-help'
(my-branch-help)$ git reset --hard 4e3cd85
HEAD is now at 4e3cd85 foo.txt added
(my-branch-help)$ ls
README.md foo.txt

标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 查看标签
git tag

# 展示当前分支的最近的 tag
git describe --tags --abbrev=0

# 查看标签详细信息
git tag -ln

# 本地创建标签
git tag [version-number]

# 默认 tag 是打在最近的一次 commit 上,指定 commit 打 tag
git tag -a [version-number] -m "v1.0 发布(描述)" [commit-id]

# 推送标签到远程仓库,保证本地创建好了标签才可以推送标签到远程仓库:
git push origin [local-version-number]

# 一次性推送所有标签
git push origin --tags

# 删除本地标签
git tag -d [tag-name]

# 删除远程标签
git push origin --delete tag [tagname]

# 切回到某个标签
git checkout -b branch_name tag_name

恢复已删除标签

首先, 需要找到无法访问的标签(unreachable tag):

1
git fsck --unreachable | grep tag

得到这个标签(tag)的 hash,然后:

1
git update-ref refs/tags/<tag_name> <hash>

这时标签(tag)应该已经恢复了。

远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 下载远程仓库的所有变动
git fetch

# 显示所有远程仓库
git remote -v

# 显示某个远程仓库的信息
git remote show [remote]

# 增加一个新的远程仓库,并命名
git remote add [shortname] [url]

# 修改远程仓库的 url
git remote set-url [remote] [url]

# 取回远程仓库的变化,并与本地分支合并
git pull [remote] [branch]

# 上传本地指定分支到远程仓库
git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --force

# 推送所有分支到远程仓库
git push [remote] --all

同步远程仓库到自己 fork 的仓库

如果一个项目多人维护,每个人都 fork 了主仓库,并修改,提交 PR,那么如果在你提交自己的修改之前,主仓库 merge 了别人的 PR,
你 fork 的仓库的 commit 就会落后于主仓库,例如会有类似 This branch is 12 commit behind ITOM-Shared-Services:master. 的提示。这个时候
直接提交你的代码,创建 PR,如果 merge 你的 PR,可能就会有冲突,怎么解决?

  1. 切换在你本地的仓库
  2. 添加 remote
    1
    git remote add itom git@github.houston.softwaregrp.net:ITOM-Shared-Services/cdf-service.git

上面的命令中 itom 是给这个 remote 命名,`git@github.houston.softwaregrp.net:ITOM-Shared-Services/cdf-service.git` 是 remote 的地址。

  1. 验证 git remote -v
  2. 在每次提交前执行 git pull itom master,可以把主仓库的最新 commits 拉去到本地。
  3. 提交代码

Rebase 和 Merge

rebase 和 merge 有什么区别

rebase

rebase 会把你当前分支的 commit 放到公共分支的最后面,所以叫变基
例如,你从 master 拉了个 feature 分支出来,然后你提交了几个 commit,这个时候刚好有人把他开发的东西合并到 master 了,这个时
候 master 就比你拉分支的时候多了几个 commit,如果这个时候你 rebase master 的话,就会把你当前的几个 commit,放到那个
人 commit 的后面。

merge

merge 会把公共分支和你当前的 commit 合并在一起,形成一个新的 commit 提交。

撤销 rebase/merge

如果 merge 或 rebase 了一个错误的分支, 或者完成不了一个进行中的 rebase/merge。 Git 在进行危险操作的时候会把原始的 HEAD 保
存在一个叫 ORIG_HEAD 的变量里, 所以要把分支恢复到 rebase/merge 前的状态是很容易的。

1
git reset --hard ORIG_HEAD

配置

配置 http 和 socks 代理

1
2
3
git config --global https.proxy 'http://127.0.0.1:8001'
git config --global http.proxy 'http://127.0.0.1:8001'
git config --global socks.proxy "127.0.0.1:1080"

配置常用的命令别名

1
2
3
4
git config --global alias.st status
git config --global alias.br branch
git config --global alias.co checkout
git config --global alias.ci commit

如:

1
git config --global alias.st status

可以使用 git st 代替 git status

或者修改配置文件,Linux 下, Git 的配置文件储存在 ~/.gitconfig。在 [alias] 部分添加快捷别名,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[alias]
a = add
amend = commit --amend
c = commit
ca = commit --amend
ci = commit -a
co = checkout
d = diff
dc = diff --changed
ds = diff --staged
f = fetch
loll = log --graph --decorate --pretty=oneline --abbrev-commit
m = merge
one = log --pretty=oneline
outstanding = rebase -i @{u}
s = status
unpushed = log @{u}
wc = whatchanged
wip = rebase -i @{u}
zap = fetch -p

配置 git log 格式

原生的 git log 不太好用,一样可以配置:

1
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就成了下面的样子:

生成压缩包

1
git archive