git 入门到精通

一、git 原理

1.1 分区原理

  • 工作区。新文件没被add到暂存区,显红色

  • 暂存区。进仓库前暂时存放区域,未对本地仓库生效。对暂存区文件修改,显蓝色。第一次创建并add到暂存区的文件,由于远程仓库没同步,显绿色。(注:存放在 “.git/index目录” )

  • 本地仓库 。暂存区commit代码放入本地仓库,通过 push 推送到远程仓库。(注:存在“.git”目录。)
    git 入门到精通_第1张图片

1.2 文件状态

在 GIt 中,文件会处于如下三种状态:

已修改(modified) - 修改了文件,但没存到本地仓库。
已暂存(staged) - 对已修改文件版本做标记,包含在下次提交的快照中。
已提交(committed) - 数据已安全存在本地仓库中。

1.3 存储文件/目录信息

然后查看下.git的目录树

$ tree -a
.
└── .git
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   ├── prepare-commit-msg.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
├── heads
└── tags
9 directories, 15 files

refs 记录当前分支的引用信息,含本地、远程分支,标签。
heads 记录本地所有分支,remotes和HEAD一样,指向对应某远程分支。

cat .git/refs/heads/mastera16b5382a9b646a...

hash值是commit节点hash值。HEAD存储当前在哪个本地分支。查看内容:

cat HEADref: refs/heads/master

Objects 目录是存储文件变化的核心。

往工作区存入测试文件a.md和test文件夹,看objects变化。

$ echo 'test1'> a.md
$ mkdir test
$ echo 'test2'> test/b.md
$ git add a.md test
$ tree -a .git/objects
.git/objects
├── 18
│   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827
├── 9d
│   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
├── info
└── pack
4 directories, 2 files

注:文件夹放到暂存区后,不会立马在objects显示,commit后才会。此时多了两文件,即修改的两个文件及修改内容。

Objects 文件名是SHA1算法哈希的“指纹”,能在本仓库和其他文件区分。文件内容是Git将信息压缩后的二进制文件。

通过 git cat-file [-t] [-p] ,看到Object类型与文件内容。

$ git cat-file -t 9daeblob #
$ git cat-file -p 9daetest1

-t 显示提交的-m信息 或 文本格式
-p 显示全部信息,包含 作者,提交日期,内容

git hash-object a.md 显示该文件hash值,与之前目录树对应。

$ git hash-object a.md9daeafb9864cf43055ae93beb0afd6c7d144bfa4

2.2 git Object的类型

git Object三种类型:Blob、Tree、Commit

文件为Blob类型,文件夹为Tree类型,提交节点为Commit类型。Git以这三种类型存储文件。目录存储的映射关系:

git 入门到精通_第2张图片

猜想:把文件提到代码库,objects应有4个目录。即2 blob,1 tree,1 commit。

$ git commit -a -m "加入到代码库中,观察objects目录变化"
[master(根提交) a16b538] 加入到代码库中,观察objects目录变化
2 files changed, 2 insertions(+) create mode 100644 a.md create mode 100644 test/b.md
$ tree -a .git/objects.git/objects
├── 18
│   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827
├── 21
│   └── d0758079bdf2c8f7514687174454c804eb0c74
├── 9d
│   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
├── a1
│   └── 6b5382a9b646a7df8d21301391f29b2f7bfb65
├── a7
│   └── 6c93bb75184ef4b34c88a301c2351ae2219407
├── info
└── pack
7 directories, 5 files

但却是5个目录,多了什么?

$ git cat-file -p 9daetest1
$ git cat-file -p 180ctest2
$ git cat-file -p 21d0100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827    b.md
$ git cat-file -p a16btree a76c93bb75184ef4b34c88a301c2351ae2219407author eleme <[email protected]> 1576979515+0800committer eleme <[email protected]> 1576979515+0800
# 加入到代码库中,观察objects目录变化
$ git cat-file -p a76c100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4    a.md040000 tree 21d0758079bdf2c8f7514687174454c804eb0c74    test

整理各自类型:9dae-blob、180c-blob、21d0-tree、a16b-commit、a76c-tree

  • 180c-blob
  • 21d0-tree
  • a16b-commit
  • a76c-tree

仔细想是两个 tree 是 git 根目录和 test 目录。

结论:每次 commit,都生成与对应的 commit hash 值。

二 初始化及配置

2.1 初始化

git init
# 或
git clone ssh://[email protected]/repo.git

2.2 配置 git config

git 配置文件存在三个位置:

  1. /etc/gitconfig : 含每个用户及仓库配置。用 git config --system 时,会从此文件读写配置变量。
  2. ~/.gitconfig~/.config/git/config :只对当前用户。 --global 选项让 Git 读写此文件。
  3. 当前仓库的 .git/config,针对该仓库。

每个级别覆盖上级配置, .git/config 会覆盖 /etc/gitconfig 配置。

git config 设 Git 外观和行为的配置变量。

显示当前设置及其来源:

git config --list --show-origin
#查看global类型的配置情况
git config --global --list

设定身份:

git config --global user.name <your name>
git config --global user.email <your email>

首选编辑器:

git config --global core.editor vim

证书缓存:

# WINDOWS
git config --global credential.helper manager
# LINUX (超时时间——秒)
git config --global credential.helper "cache --timeout=3600"
# MACO
git config --global credential.helper osxkeychain

2.3 配置SSH

Git 服务大部分用 SSH 公钥认证,默认 SSH 密钥存在 ~/.ssh 目录。 进该目录查看密钥:

ls ~/.ssh
authorized_keys2  id_dsa       known_hosts
config            id_dsa.pub

如有id_dsaid_dsa.pub说明已生成,后面步骤可省

id_dsa 或 id_rsa 命名的文件,其中一个带 .pub 扩展名。 .pub 文件是公钥,另个是私钥。

$ ssh-keygen

# 一路回车回车即可,以下为输出内容,仅供参考
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
/root/.ssh/id_rsa already exists.
Overwrite (y/n)? y
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:xFntIEM+aKyuc5dJmCvq1xnccqCaMAcK3kBClYXc/J0 [email protected]
The key's randomart image is:
+---[RSA 2048]----+
|..o.*. .. ..     |
|.. + + +oo. .    |
|o     = Bo.o     |
|o.   + o E  .    |
|+.o +oo S        |
|+..+o+.o         |
|.oo oo=o         |
| ooooo+          |
|ooo+ .           |
+----[SHA256]-----+

首先 ssh-keygen 确认密钥存储位置(默认 .ssh/id_rsa),然后它要求输入两次密钥口令。如果不想用密钥时输入口令,可留空。

2.4 别名

# 创建别名
git config --global alias.<alias-name> ""
# 使用别名
git <alias-name> <more optional arguments>

常用 别名

# 撤销上次提交
git config --global alias.undo "reset --soft HEAD^"
# 将暂存区更新到上次提交 (不改变提交信息)
git config --global alias.amend "commit --amend --no-edit"
# 压缩的状态输出
git config --global alias.st "status -sb"
# 用 GRAPH 为日志着色
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'"
# 删除所有已合并分支
git config --global alias.rmb "!git branch --merged | grep -v '*' | xargs -n 1 git branch -d"
# 贡献排行
git config --global alias.rank "shortlog -n -s --no-merges"

# 最爱的 git 别名
alias g='git'
alias glog='git log --oneline --decorate --graph'
alias gst='git status'
alias gp='git push'
alias ga='git add'
alias gc='git commit -v'
alias yolo='git push --force'
# 每周站会汇报工作时用
git-standup() {   
	AUTHOR=${AUTHOR:="`git config user.name`"}
  since=yesterday   
  if [[ $(date +%u) == 1 ]] ; then       
  	since="2 days ago"   
  fi
	git log --all --since "$since" --oneline --author="$AUTHOR"
}
# 其他
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

2.5 gitignore

.gitignore 忽略文件或目录,不纳入版本控制。

推荐 常用模板,如:Java、Nodejs、C++ 的模板等。

2.6 命令表

本节选择性介绍 git 中较常用的命令行场景。
git 入门到精通_第3张图片

2.7 高级用法表

git 入门到精通_第4张图片

三、本地仓库命令

3.1 分区

3.1.1 工作区
3.1.1.1 提交
git add [-u][-p file] .|filename			

-u 添加被跟踪的的变化到暂存区
-p 添加目录内到某些改动到暂存区

3.1.1.2 查看
git status

-sb ,-s 短显示,-b 分支
–show-stash

注意:执行 git statusgit branch -avv 查看仓库状态和分支状态

3.1.1.3 清除
git clean -nfd

-n 预览删掉的文件,但不执行删除操作(强烈推荐)
-f 强删未 untracked 文件(含.gitignore但不含.gitignore规则文件/目录),无法恢复
-d 删掉未 untracked 工作区目录
-x 删除包含在.gitignore里的规则文件/目录

3.1.2 暂存区 cached
3.1.2.1 提交

git commit [–amend] -m [-a] ‘message’

-m 提交到仓库的备注信息
–amend 编辑上次提交信息
-a 对已跟踪文件无需add,提交到仓库

git commit -m "xxx"
git commit --no-verify -m "xxx"
git commit -t templateFile
git commit -F
3.1.2.2 清除暂存区

git rm [-n] [-r] [-f] [–cached] [filename|dirname|.]

-n 预览删除暂存区文件/目录
-r 递归删除暂存区目录
–cached 仅删暂存区文件或目录,保留工作区
-r --cached 删暂存区文件/目录,保留工作区,等同 git reset – fielname
-f [filename|.] # 删暂存区和工作区文件,等同 git rm --cache filename && git clean -f filename

3.1.2.3 修改 commit 信息

Commit 不可变。但可用新 commit 覆盖原始 commit,请勿在已 push 的 commit 中用

git commit --amend --no-edit
git commit --amend -m "更好的提交日志"
# 在上次提交中附加些内容,保持提交日志不变
git add .&& git commit --amend --no-edit

# 空提交,用来重新触发 CI 构建
git commit --allow-empty -m "chore: re-trigger build"

https://git-scm.com/docs/git-commit#Documentation/git-commit.txt—amend

3.1.3 仓库区/版本区
3.1.3.1 提交
git push			#数据从版本库中发送到远程仓库
3.1.3.2 更新本地
git pull   		#从远程代码库拉取到本地仓库
git fetch origin
git checkout master
git reset --hard origin/master
3.1.3.3 清理本地仓库
git reset [--soft|--mixed|--hard] 版本库ID

–soft 仅撤销版本库,不改暂存区和工作区
–mixed 仅撤销交版本库和暂存区,不改工作区
–hard 将工作区、暂存区和版本库恢复指定版本

3.1.3.4 对比 diff
git diff				# 工作区被跟踪与暂存区对比
git diff head			# 工作区被跟踪与版本库对比
git diff --cached		# 暂存区与版本库对比

git diff 终端使用不频繁,除少量改动。倾向 GUI 工具更直观。

3.2 历史 log/relog

注:commit后才算历史版本,仅add不算

3.2.1 log
# 从最新提交开始,显示所有提交记录(hash,作者信息,标题和时间)
git log
# 等同 git log --pretty=oneline --abbrev-commitgit log --oneline
3.2.1.1 查看

git log [–reverse|–oneline]

–reverse 按时间正序排列的信息
–oneline 每个提交在一行内显示
–graph 提供类似 GUI 工具的 log 展示
–all 展示所有
–grep 过滤 --grep=‘homepage’
–author 显示某用户的提交 --author=“username”
n 指定最近几个提交可带 -/+ 数字 git log n 5
-p 显示某文件的所有修改 git log -p

git log --oneline -5
git log --graph --date=relative --pretty=tformat:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%an %ad)%Creset'
3.2.1.2 查找 Commits 和更改
# 通过 commit 信息查找 (所有分支)
git log --all --grep=''
# 通过 commit 信息查找 (包含 reflog)
git log -g --grep=''

# 通过更新的内容查找
git log -S ''
# 通过日期范围查找
git log --after='DEC 15 2019' --until='JAN 10 2020'
3.2.2 reflog

git reflog 记录所有行为,含 rebase、merge、reset

当不小心硬回滚或变基错误,可找到 commit 然后回滚。仅本地有用。

git reflog
## reflog 查看本地以往版本号
git reflog -5

3.3 搜索 grep

# 从当前目录文件中查找文本内容
git grep "Hello"
# 在某一版本中搜索文本
git grep "Hello" v2.5

3.4 分支 brand

3.4.1 初探

在本地 master 上。通过 git branch 创建其他分支。

注:本地分支未被跟踪的文件,所有分支均可看到该文件,且其他分支都可修改提交,但commit后就只能由commit分支查看

3.4.2 创建

master 分支需先 commit 才能生成 master 分支,否则报错:fatal: Not a valid object name: 'master'.

git branch feature/dev
3.4.3 查看
git branch [-r][-a]

-r 查看远程分支
-a 查看全部分支,白色表本地有,红色仅远程存在

3.4.4 修改

修改分支名称

git branch [-M old new]

3.4.5 删除
git branch [-d branchName]
# 删除远程仓库不存在的分支
git fetch -p
# 移除所有包含 `greenkeeper` 的分支
git fetch -p && git branch --remote | fgrep greenkeeper | sed 's/^.\{9\}//' | xargs git push origin --delete
3.4.6 本地关联远程分支
git branch --set-upstream-to=origin/xxx
git branch --set-upstream-to origin xxx
3.4.6 对比
## 查看 当前分支与 master 的不同
git diff master..my-branch  
3.4.7 切换 checkout/branch

可基于分支,提交,标签切换到对应记录。一般用来热修复、老版本加新特性。

# 切换分支
git checkout feature/dev 	
# 创建并切换分支,等价 git branch dev && git checkout dev
git checkout -b dev

git checkout --track origin/feature-test
# 放弃单文件修改,注意不要忘记中间的"--",不写就成了检出分支了!
git checkout -- filepathname
# 放弃所有文件修改
git checkout .
# 跳到之前分支
git checkout -

# 切换到其他分支并查看分支信息
git branch -vv* feature/dev  a16b538 
#加入到代码库中,观察objects目录变化  feature/wuya 
#a16b538 加入到代码库中,观察objects目录变化  master       
#a16b538 加入到代码库中,观察objects目录变化

分支当前的指针指向最近一次commit节点。谁建的分支,就沿用谁指针。

注:未被放入代码库的文件在分支切换时被抛弃,造成严重后果。

3.4.8 合并 merge/rebase

分支合并有:merge 和 rebase 两种。两者都从一个分支获取并合到当前分支。

3.4.8.1 merge

merge:自动建新 commit,如冲突则修改后重 commit。
git 入门到精通_第5张图片

每次都记录详细的 commit,但 commit 频繁时,分支较乱。

3.4.8.2 rebase

rebase:找公共节点,直接合并前 commit 历史,也叫变基。得到简洁分支历史,去掉 merge commit。但如果合并出问题,无痕迹,不好定位。
git 入门到精通_第6张图片

  • git rebase --abort 放弃合并,回到rebase前状态。
  • git rebase --continue 结合 “git add 文件” 命令,一步步地解决冲突。
  • git rebase --skip 引起冲突的 commits 丢弃掉。

例1:引用网上归纳的git rebase工作流:

git rebase while(存在冲突) {
  # 找到当前冲突文件,编辑解决冲突    
  git status    # 显示工作路径下已修改的文件
  git add -u    
  git rebase --continueif( git rebase --abort )
  break;
}

注:最好不在公共分支 rebase,如果前后基本没人动分支,推荐rebase。

例2:想要 rebase 最近 3 个提交:

  • git rebase -i HEAD~3
  • 保留第一行 pick,剩余提交替换为 squash 或 s
  • 清理提交日志并保存
pick 64d26a1 
feat: add index.jss 45f0259 
fix: update index.jss 8b15b0a 
fix: typo in index.js

修正:想在提交 fed14a4c 加上些内容。
git 入门到精通_第7张图片
git 提交分支

git add .git commit --fixup HEAD~1 #或用哈希值(fed14a4c)
git rebase -i HEAD~3 --autosquash #保存并退出文件(VI输:wq)

rebase 时在每个提交上执行命令

如果特性多,一个分支有多个提交。或测试失败,希望找到导致测试失败的提交。用 rebase --exec 命令在每个提交上执行命令。

# 在最近3个提交上运行 `npm test` 命令
git rebase HEAD~3 --exec "npm test"

git 入门到精通_第8张图片

3.4.9 分支合并
# 将dev合并到master
git checkout master
git merge dev
git merge --no-ff branchName

合并完后,删除dev分支

# 删除dev分支时,注意当前所在的分支不能是dev分支
git branch -d dev				# 本地删除分支dev
git branch -D issues1234  		# 本地强删分支issues1234
git push origin  :issues1234  	# 推到远程
3.4.6 部分合并

从多个分支挑取部分 commit 合并到同个地方。

类似变基,挑的 commit 若无冲突则追加。有就中断,解决后 --continue

git cherry-pick commit-sha1
git cherry-pick master~4 master~2
git cherry-pick startGitSha1..endGitSha1
3.4.9 冲突分支

在合并时产生。分支合并是 tree 和 tree 合并。执行 git merge master 时。git 先找到两分支从哪个指针创建,称“merge base”。然后检查两次 tree 是否一致,如不一致说明文件被修改。接下来就某文件分类讨论:

git 入门到精通_第9张图片

  • 在节点 3、6,merge base 的 hash 值相同。说明没修改。无冲突。
  • 在节点 6 和 merge base 或节点 3 和 merge base 的 hash 值相同,此时直接更新文件变化。
  • 在节点 6,merge request,master 的 hash 值不同,产生冲突。

解决冲突

<<<<<<< HEADCreating a new branch is quick & simple.=======Creating a new branch is quick AND simple.>>>>>>> feature1

<<<<<<<,=======为自己;=======,>>>>>>>为别人。此时需与开发者商定,解决冲突。如保留自己代码,删掉别人代码。

3.4.10 总结

常从master创建新分支,具体操作如下:

# master创建新分支:
git checkout master
git checkout -b issues1234  	# 从master分支创建issues1234分支
git push origin issues1234
git add ..
git commit -m "***"
git push origin issues1234

注意:将本地分支 branch1 推到远端 branch2 操作步骤:

git push origin branch1:branch2

3.5 撤销checkout

git checkout -- a.html

git add 但未 git commit,执行操作后,恢复到git add 后状态

注:一旦执行 git commit -m "*",就不能用上面回退。

3.6 回滚revert/reset

版本回退离不开reset和revert。

3.6.1 revert

git revert commit_id 产生与 commit_id 相反提交,即 commit_id 添加, revert 删除。

git revert 后,将退到上个 commit 版本。平稳回滚代码,保留提交记录,不让协作人冲突。一般用于 master 回滚。

# 撤销远程修改(创建个新提交,并回滚到指定版本):
$ git revert <commit-hash>

git log 查看日志后,因提交次数太多,且含 merge 操作。如有不干净的提交记录,完成从 C 到 N 版本 revert,需倒序执行 revert 操作几十次,如中途顺序错一次,最终结果可能不对。

另在 merge 时,会把 merge 产生次新提交,而 revert 在 merge commit 时需指定 m 参数,mainline 是主线,是保留代码的主分支,从 feature 往 develop 分支合并,或由 develop 合到 master 提交,但 feature 分支互相合并时,哪知道哪个是主线啊。

所以 revert 文案被废弃。

3.6.2 reset

然后考虑 reset , reset 也能回到某次提交,但与 revert 不同, reset 是将提交的 HEAD 指针指到某次提交,之后提交记录会消失。

由于在 feature 分支开发,在 feature 分支将代码回退到某版本后,其合到 develop 分支时却报错。因为 feature 分支回退提交后,在 git 的 workflow 里,feature 分支落后于 develop 分支,而合并向 develop 分支,又要和 develop 分支保持最新同步,需将 develop 分支合并到 feature 分支,而合并后,原被 reset 的代码又回来了。

这时另个可选项是在 master 上执行 reset,用 —hard 选项完全抛弃旧代码,reset 后再强制推到远端。
git 入门到精通_第10张图片
git reset分为三种模式:soft、mixed、hard

回退至上个版本

# 将HEAD重置到上次提交版本,并将之后的修改标为未添加到缓存区的修改
$ git reset HEAD/commit-hash

# 将HEAD重置到指定的版本,并放弃工作目录下的所有修改
$ git reset --hard HEAD/commit-hash

# 用远端分支强制覆盖本地分支
$ git reset --hard <remote/branch> e.g., upstream/master, origin/my-feature

git reset --soft HEAD~1
git reset --mixed commit_sha1
git reset --merge commit_sha1
# 将HEAD重置到上次提交的版本,并保留未提交的本地修改
$ git reset --keep <commit>
# 放弃某个文件的本地修改
$ git checkout HEAD <file>

回退指定版本,每次 commit 都产生个版本号。

# 执行下面命令后,commit-hash 提交后记录会被删除,谨慎使用
git reset --hard <commit-hash>
git push -f

重置暂存区和工作区为指定 commit 节点。当前分支未 commit 代码被清除。

git reset --soft 版本号

保留工作目录,并把指定commit节点与当前分支差异都存入暂存区。即没被commit的也保留。一般用soft模式,既保留暂存区,又能reset到某分支。

git reset 版本号

不带参,即mixed模式。保留工作目录,并把工作区,暂存区及与reset差异放到工作区,然后清空暂存区。执行后只要有差异文件就变红色,难以区分。

3.6.3 提交后回滚
# 获取所有操作历史
git reflog
# 重置到相应提交
git reset HEAD@{4}
#或
git reset --hard <提交的哈希值>
3.6.4 对push版本回退
# 第一步:本地回退到指定的版本
git reset --hard 版本号 		
# 第二步:将远程的也回退到指定版本
git push -f origin dev   

四、代码暂存 stash

当前分支切到其他分支而不想 commit 时,可用 git stash 将数据都暂存Git栈。(注:仅暂存被跟踪文件,用前应先提交到本地,再 stash)

4.1 添加 save

将当前更改的代码储藏,以后恢复使用

git stash						# 保存所有正在追踪的文件
git stash save "日志信息"
git stash -u save stashName  	#-u 包含未被追踪的文件
git stash push -m "更改了 xx"

4.2 查看 list

查看本地所有 stash

git stash list

4.3 恢复 pop/apply

# 恢复同时把 stash 内容删掉
git stash pop 
git stash pop stash@{0}
# 如要恢复第一个就执行
git stash apply stash@{0}

git stash apply  # 恢复stash,但 stash 内容不删除
git stash drop   # 在上面操作的基础上,删除 stash

pop/apply区别

git stash pop stash@{id}		# 将对应 stash id 从 stash list 删除,
git stash apply stash@{id}  	# 会继续保存 stash id。

4.4 删除drop/pop/clear

# 获取并删除暂存项
git stash apply stash@{1}
git stash drop stash@{1}
# 或用一条命令
git stash pop stash@{1}
# 将stash空间清空
git stash clear

git stash show stash@{1}

4.5 储藏 stash 更改而不提交

stash 将当前更改搁置。返回当前状态索引,并在稍后应用已储藏更改。

默认储藏当前跟踪文件的更改,新文件被忽略。可独立创建、应用多个 stash。

https://git-scm.com/docs/git-stash

创建

# 创建新 STASH
git stash
# 创建新 STASH (含未追踪的更改)
git stash -u/--include-untracked
# 创建新 STASH 并命名
git stash save ""
# 交互式储藏
git stash -p

查看

git stash list	# 列出所有 STASH (为其他命令提供"n")

git stash show			# 浏览 STASH 内容
git stash show -p		# 浏览 STASH 差异

应用

# 应用上个 STASH (删除 stash)
git stash pop
# 应用上个 STASH (保留 stash)
git stash apply
# 应用特定 STASH (n = stash 列表序号)
git stash pop/apply stash@{n}
# 从 STASH 创建新分支 (n = stash 列表序号)
git stash branch <new branch name> stash@{n}
# 从 STASH 应用单文件 (n = stash 列表序号)
git checkout stash@{n} -- <filepath>

清理

git stash drop stash@{n}	# 删除特定STASH (n = stash列表序号)
git stash clear				# 删除所有 STASH

五、标签管理

5.1、打分支标签 tag

git tag v1.0.0

5.2 查看标签

git show v1.0.0  

5.3、创建说明标签

git tag -a v1.0.0 -m "version 1.0.0 released" 34372b05(commit版本号) 
# 查看标签详细信息
  

-a指定标签名
-m指定说明文字

5.4、举例

如周五给周一某commit打标签,执行以下步骤:

# 1、查log日志,找到相应 commit 版本号
git log --pretty=oneline --abbrev-commit
# 如给"34372b05"打标签fix bug34372b05 
# 2、给指定的 commit 打标签
git tag v1.0.0 34372b05
# 3、创建的标签只存在本地,推至远程
git push origin v1.0.0
# 4、一次性推送未推至远程的本地标签
git push origin --tags
# 5、查询所有标签
git tag
# 6、查询标签详细信息
git show v1.0.0
# 7、删除本地标签
git tag -d v1.0.0
# 8、删除远程标签
# 先从本地删除
git tag -d v1.0.0
# 然后从远程删除
git push origin :refs/tags/v1.0.0
# 9、最后在gitlab上查看是否删除
git push origin --tags

六、远程仓库命令

6.1 查看 show

git remote -v				#列出所有的仓库地址
git remote show origin 		#查看远程仓库的信息

git remote rename pb paul 	#修改别名
git remote rm paul 			#删除别名

6.2 添加 add

git remote add origin url
git remote set-url  origin(或其他上游域) url

6.3 获取信息 rev-parse

git rev-parse 快速获取部分git 仓库信息,弄脚本时用

git rev-parse --short HEAD --verify
git rev-parse --show-toplevel
git rev-parse --git-dirgit rev-parse --all

6.4 更新 fetch/pull

本地同步远程删除的分支

git fetch <remote> 			# 从远程更新代码到本地但不合并
git pull <remote> <branch> 	# 从远程更新代码到本地且合并
git fetch --all
git fetch origin -p  
# 清除无远程信息的分支,这样 git branch -a 就不会拉取远程已删除分支

注,仅更新本地的远程分支信息,即 git branch -avv 时, remotes 开头的分支信息。

fetch 刷新本地仓库的远程分支信息,需联网。若本地 master 最新,执行 git pull 拉取远程分支到本地,当前面执行过 git fetchgit rebase origin/master 实现 “使本地 master 分支基于远程 master 分支”,rebase 常用

6.5 拉取 pull

git pull大多带--rebase(-r) (变基形式拉取合并代码),保持分支一条线。

默认pullff模式,多数情况会产生新 commit,部分参数与 merge一致。

# 从远程更新代码到本地且合并
git pull github master
git pull --rebase origin master # 远程拉取与合并可用git pull,变基也行,变基过程可随时 abort 停止。简写 git pull -r origin master

6.6 推送 push

# 将本地版本推送到远程端
$ git push remote <remote> <branch>
# 删除远程端分支
$ git push <remote> :<branch> 
$ git push <remote> --delete <branch> 

git push origin localbranch # 发布分支到远程地址
git push -d origin branchName
git push --tags  			# 发布标签、上传标签
git push --follow-tags
git push -f origin branchName
git push --force-with-lease
git push -u origin master # 设置关联,自动推送。后续推送用git push即可,无需指定 origin 和 master

安全强制推送

git push --force 可覆盖远程变更,但不推荐!有破坏性且危险,因为它无条件生效,且破坏其他人已推的 commit。这对他人代码仓库不一定致命,但改变历史记录并影响其他人并不是个好主意。

推荐 git push --force-with-lease

git 不会无条件覆盖上游远程仓库,先检查是否有本地不可用的远程更改。如有则失败并显示 “stale info” 消息,并告知需先运行 git fetch

https://git-scm.com/docs/git-push#Documentation/git-push.txt—force-with-leaseltrefnamegt

6.7 删除

查远程有但本地无的分支:git branch -a

删掉没与远程分支对应的本地分支:git fetch -p

七、使用技巧

7.1 添加 hunk

git add不仅添加文件变更,--path / -p 参数还可交互式暂存区块。

补丁命令

y = 暂存区块
n = 不暂存这个区块
q = 退出
a = 暂存当前文件的此区块以及所有剩余区块
d = 不暂存当前文件的此区块以及所有剩余区块/ = 查找区块 (正则表达式)s = 划分成更小的区块
e = 手动编辑区块
? = 打印帮助说明
g = 选择要前往的区块
j = 将区块设为未定,查看下一个未定区块
J = 将区块设为未定,查看下一个区块
k = 将区块设为未定,查看上一个未定区块

https://git-scm.com/docs/git-add#Documentation/git-add.txt–i

7.2 空运行 Dry Run

许多 git 操作有破坏性。要避免这种结果。许多命令都支持dry-run,在产生结果前对其检查,但选项不完全一致:

git clean -n/--dry-rungit add -n/--dry-run
git rm -n/--dry-run
# GIT MERGE 模拟 DRY-RUN
git merge --no-commit --no-ff <branch>
git diff --cachedgit merge --abort

注意,git commit -n 根本不是 dry-run!它是 --no-verify ,作用是忽略所有 pre-commit/commit-msg

7.3 修改历史

修改仓历史不仅限于修改上次提交信息,用 git rebase 可修改多个提交:

# 提交范围
git rebase -i/--interactive HEAD~<number of commits>
# 该 hash 之后的所有提交
git rebase -i/--interactive <commit hash>

在配置的编辑器中倒序列出所有 commit:

#   
pick 5df8fbc revamped logicpick ca5154e README typos fixed pick a104aff added awesome new feature

通过更改编辑器中的实际内容,为 git 提供方案,说明如何 rebase:

# p, pick   = 使用提交而不更改
# r, reword = 修改提交信息
# e, edit   = 编辑提交
# s, squash = 汇合提交
# f, fixup  = 类似"squash",但是会丢弃提交信息
# x, exec   = 运行命令 (其余行)
# d, drop   = 移除提交

保存后,git 运行该方案重写记录。e, edit 暂停 rebase,可编辑代码仓库的当前状态。完成编辑后,运行 git rebase --continue

如果出问题(如合并冲突),需重新开始,用 git rebase --abort

https://git-scm.com/docs/git-rebase

变基在项目中很频繁。如开发新 feature, 遵循最小化代码提交理念。在整个功能开发完时,会有很多 commit,用 rebase 可让 commit 记录很干净。

git rebase -i git-sha1|branch(HEAD)
git rebase --continuegit rebase --skip
git rebase --abort
  • pick: 保留该 commit(采用)
  • edit: 一般提交的多了,可用这个把东东拿回工作区拆分更细的 commit
  • reword: 可重新修改 commit msg
  • squash: 内容保留,把提交信息往上个 commit 合并进去
  • fixup: 保留变动内容,但抛弃 commit msg
  • drop: 较少用,无用的改动你会提交么!!!

温馨提示:

  • 本地提交前,最好把基准点变为需合并的分支,当提交 PR/MR 时就不会冲突(本地解决冲突)
  • 不在公共分支上变基!!!一变其他协作者基本都一堆冲突!除非有很清晰的分支管理机制

7.4 存档跟踪文件

可用不同格式(ziptar)压缩特定引用的跟踪文件:

git archive --format <format> --output <filename> <ref>

可是个分支 commit hash 或标签。 https://git-scm.com/docs/git-archive

7.5 额外提醒:单破折号

有快捷方式可表示刚用的分支:一个单破折号-

git checkout my-branch
# 当前分支:my-branch
git checkout develop
# 当前分支:developgit merge,将 my-branch 合到 develop

单破折号等同于@{-1}

https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt-ltbranchgt

八、Git 分支设计规范

参考文章:Git 在团队中的最佳实践–如何正确使用 Git Flow

实际开发中的最佳实践策略 Git Flow 可归纳为:

  • master - 主分支,用于发布生产环境,且只能从其他分支合并,不能改。
  • develop - 主开发分支,用于部署到测试环境(FAT),含发布下个 release 代码,主要从其他分支合并,如 feature 分支。
  • feature - 主要开发新功能,开发完成后合并 develop 分支进入下个 release。一旦该需求上线,便将其删除。
  • release - 当发布新 release 时,基于 Develop 分支创建 release 分支,完成 release 后,合到 master 和 develop 分支。
  • hotfix - 在 master 发现新 Bug ,需创建 hotfix, 完成 hotfix 后合回 master 和 develop 分支,hotfix 的改动进入下个 release

先说系统开发过程中常用的环境。

简称 全称
DEV Development environment
FAT Feature Acceptance Test environment
UAT User Acceptance Test environment
PRO Production environment
  • DEV 环境:开发者调试使用。
  • FAT 环境:功能验收测试环境,测试环境下测试使用。
  • UAT 环境:用户验收测试环境,生产环境下测试使用。
  • PRO 环境:就是生产环境。

如项目域名:http://www.abc.com,那相关环境的域名配置如下:

  • DEV 环境:本地配置虚拟域名即可
  • FAT 环境:http://fat.abc.com
  • UAT 环境:http://uat.abc.com
  • PRO 环境:http://www.abc.com

接下来,针对不同的环境来设计分支。

8.1 分支

分支 名称 环境 可访问
master 主分支 PRO
release 预上线分支 UAT
hotfix 紧急修复分支 DEV
develop 测试分支 FAT
feature 需求开发分支 DEV

8.1.1 master 分支

master 为主分支,用于部署正式环境(PRO),由 releasehotfix 分支合并,任何情况不许在 master 分支改代码。

8.1.2 release 分支

release 为预上线分支,用于部署预上线环境(UAT),保持与 master 一致,一般由 develophotfix 分支合并,不建议在 release 分支修改代码。

如果在 release 分支测出问题,需回归验证 develop 分支看否存在此问题。

8.1.3 hotfix 分支

hotfix 为紧急修复分支,命名规则为 hotfix- 开头。

当线上出现紧急问题时,需基于 releasemaster 分支创建 hotfix 分支,修完后,再合并到 releasedevelop 分支,上线后便将其删除。

8.1.4 develop 分支

develop 为测试分支,用于部署到测试环境(FAT),始终保持最新完成及 bug 修复后的代码,可根据需求大小确定是由 feature 分支合并,还是直接在上面开发。满足测试的代码才能往上面合并或提交。

8.1.5 feature 分支

feature 需求开发分支,规则为 feature- 开头,一旦需求上线,便将其删除。接下来举几个开发场景。

8.2 开发场景

8.2.1 新需求加入

有订单管理需求,先建 feature-order 分支。该分支基于哪个分支创建?如有 未测完的需求,就基于 master 创建。如没有 未测试完毕 需求,就基于 develop 创建。

  1. 需求在 feature-order 分支开发完,提测时先确定 develop 不存在未测试完的需求,这时研发才能将代码合并到 develop (测试环境)供测试者测试。
  2. 测试者在 develop (测试环境) 测试通过后,研发再将代码发到 release (预上线环境)供测试人员测试。
  3. 测试在 release (预上线环境)测试通过后,研发再将代码发布到 master (正式环境)供测试人员测试。
  4. 测试在 master (正式环境) 测试通过后,研发需删除 feature-order分支。
8.2.2 普通迭代

有个订单管理的迭代需求,如果开发工时 < 1d,直接在 develop 开发,如果开发工时 > 1d,那就需创建分支,在分支上开发。

开发后的提测上线流程 与 新需求加入流程一致。

8.2.3 修复测试环境 Bug

develop 测试出 Bug,如果修复工时 < 2h,直接在 develop 修复,如果修复工时 > 2h,需在分支上修复。

修复后的提测上线流程 与 新需求加入流程一致。

8.2.4 修改预上线环境 Bug

release 测出Bug,首先想 develop 分支是否同样存在该问题。

如存在,修复流程 与 修复测试环境 Bug流程一致。

如不存在,这种可能性较少,大部分是数据兼容问题,环境配置问题等。

8.2.5 修改正式环境 Bug

master 测出Bug,首先想 releasedevelop 分支是否存在同样问题。

如存在,修复流程 与 修复测试环境 Bug流程一致。

如不存在,这种可能性也较少,大部分是数据兼容问题,环境配置问题等。

8.2.6 紧急修复正式环境 Bug

需求在测试环节未测出 Bug,上线运行后出现 Bug,需紧急修复。

个人理解紧急修复即没时间验证测试环境,但建议验证下预上线环境。

  • release 分支有 未测完需求,就基于 masterhotfix-xxx 分支,修复完发 master 验证,验完后将 master 合到 releasedevelop 分支,同时删 hotfix-xxx 分支。
  • release 分支无 未测完需求,但 develop 分支存有 测完需求,就基于 releasehotfix-xxx 分支,修复完后发 release 验证,后面流程与上线流程一致,验完后将 master 代码合到 develop 分支,同时删掉 hotfix-xxx 分支。
  • releasedevelop 分支无 未测试完需求, 就在 develop分支修复完后,发到 release 验证,后面流程与上线流程一致。
8.2.7 并行提测

在一个项目中并行开发两个需求,并行提测,但上线日期不同。

两种方案:

  • 再部署一套可供测试人员测试的分支
  • 使用 git cherry-pick “挑拣”提交

8.3 Commit 日志规范

建议参考规范:type(scope):subject

比如:fix(首页模块):修复弹窗 JS Bug。

type 表示 动作类型,可分为:

  • fix:修复 xxx Bug
  • feat:新增 xxx 功能
  • test:调试 xxx 功能
  • style:变更 xxx 代码格式或注释
  • docs:变更 xxx 文档
  • refactor:重构 xxx 功能或方法

scope 表 影响范围,分为:模块、类库、方法等。

subject 表 简短描述,最好不超 60 字,如有关 Bug 的 Jira 号,建议在描述中添加。

九、Git commit 规范

9.1 用什么规范?

较流行方案是 约定式提交规范(Conventional Commits),它受 Angular 提交准则启发,并在很大程度上以其为依据。约定式提交规范是种基于提交消息的轻量级约定。它提供一组用于创建清晰的提交历史的简单规则;这使编写基于规范的自动化工具变得更容易。这个约定与 SemVer 吻合,在提交信息中描述新特性、bug 修复和破坏性变更。它的 message 格式如下:

<类型>[可选的作用域]: <描述>
[可选的正文]
[可选的脚注]

9.2 Quick Start

9.2.1 全局安装commitizen & cz-conventional-changelog

commitizen是个撰写 commit message 工具,用于代替 git commit 指令,而 cz-conventional-changelog 适配器提供 conventional-changelog 标准(约定式提交标准)。基于不同需求,也可用不同适配器。

npm install -g commitizen cz-conventional-changelogecho '{ "path": "cz-conventional-changelog" }' > ~/.czrc

安完后,可用 git cz 来取代 git commit。全局模式下,需要 ~/.czrc 配置文件,为commitizen指定Adapter。

9.2.2 项目内安装commitlint & husky

commitlint 对 commit message 格式校验,husky 负责提供更易用的 git hook。

Use npm:

npm i -D husky @commitlint/config-conventional @commitlint/cli

Use yarn:

yarn add husky @commitlint/config-conventional @commitlint/cli -D

commitlint 只能做格式规范,无法触及内容。对内容质量的把控只能靠自己。

9.2.3 添加相应配置

创建 commitlint.config.js:

# In the same path as package.json
echo 'module.exports = {extends: ["@commitlint/config-conventional"]};' > ./commitlint.config.js

引入 husky:

# package.json
...,"husky": {    "hooks": {      "commit-msg": "commitlint -e $GIT_PARAMS"    }}
9.2.4 使用

执行 git cz 进入 interactive 模式,根据提示依次填写:

1.Select the type of change that you're committing 选择改动类型 (<type>)
2.What is the scope of this change (e.g. component or file name)? 填写改动范围 (<scope>)
3.Write a short, imperative tense description of the change: 写一个精简的描述 (<subject>)
4.Provide a longer description of the change: (press enter to skip) 对于改动写一段长描述 (<body>)
5.Are there any breaking changes? (y/n) 是破坏性修改吗?默认n (<footer>)
6.Does this change affect any openreve issues? (y/n) 改动修复了哪个问题?默认n (<footer>)

生成的commit message格式如下:

(): 

填完后,husky 调 commitlint 对 message 校验格式,默认 type、subject 为必填。

任何 git commit 指令的 option 都能用在 git cz 指令上, 如 git cz -a

9.3 Commit规范在rrd-fe落地使用

针对团队使用情况,拟定 commit message 每部分的填写规则。

9.3.1 type

type 必填,指定 commit 类型,约定 feat、fix 两个主要 type ,及 docs、style、build、refactor、revert 五个特殊 type,其余 type 暂不用。

# 主要 type
feat: 增加新功能
fix: 修复bug
# 特殊 type
docs: 只改动文档相关内容
style: 不影响代码含义的改动,如去空格、改缩进、增删分号
build: 构造工具的或外部依赖的改动,如webpack,npm
refactor: 代码重构时使用
revert: 执行git revert打印的message
# 暂不用type
test: 添加测试或者修改现有测试
perf: 提高性能的改动
ci: 与CI(持续集成服务)有关的改动
chore: 不修改src或test的其余修改,如构建过程或辅助工具的变动

当一次改动包括主要type与特殊type时,统一采用主要type

9.3.2 scope

scope 必填,描述改动范围,格式:项目名/模块名,如:node-pc/common rrd-h5/activity,而we-sdk不需指定模块名。如一次 commit 修改多个模块,建议拆成多次commit,以便更好追踪和维护。

9.3.3 body

body 详细描述,主要描述改前的情况及动机,小修改不作要求,但重大需求、更新等须加 body 说明。

9.3.4 break changes

break changes 指明是否产生破坏性修改,涉及 break changes 改动须指明该项,类似版本升级、接口参数减少、接口删除、迁移等。

9.3.5 affect issues

affect issues 指明是否影响某问题。如用 jira 时,在 commit message 中可写影响的JIRA_ID,若开启该功能需先打通jiragitlab。参考:docs.gitlab.com/ee/user/pro…

填写方式例如:

re #JIRA_IDfix #JIRA_ID
9.9.4 示例

完整commit message示例
git 入门到精通_第11张图片
相应git log
git 入门到精通_第12张图片

十、常见问题

10.1 编辑提交

10.1.1 刚提交了什么

不确定提交了什么时,用如下命令显示最近一次提交(commit):

(master)$ git show
# 或
$ git log -n1 -p
10.1.2 提交信息写错

如提交未推, 可用如下方法修改提交信息

$ git commit --amend

打开默认编辑器编辑. 也可用一条命令一次完成:

$ git commit --amend -m 'xxxxxxx'

如提交已推, 可修改这次提交(commit)然后强推(force push), 但不推荐。

10.1.3 提交的用户名和邮箱不对

如果是单个提交(commit),修改它:

git commit --amend --author "New Authorname "

如要修改所有历史, 参考 git filter-branch指南页.

10.1.4 从提交里移除文件

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

$ git checkout HEAD^ myfile
$ git add -A
$ git commit --amend

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

10.1.5 删除最后一次提交

如要删除已推提交(pushed commits),可用下面方法。但会不可逆的改变历史,也会搞乱已从该仓库拉取(pulled)人的历史。如果不确定,千万别做。

$ git reset HEAD^ --hard
$ git push -f [remote] [branch]

如未推远程, 可把 Git 重置(reset)到提交前状态(同时保存暂存变化):

(my-branch*)$ git reset --soft HEAD@{1}

只在没推送前有用。如已推, 唯一安全能做的是 git revert SHAofBadCommit, 那会创建新提交用于撤消前提交的变化(changes);或如果推的分支是 rebase-safe 的 (其它开发者不会从该分支拉), 需用 git push -f;更多 参考。

10.1.6 删除任意提交(commit)

警告:万不得已时才如下做.

git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
git push -f [remote] [branch]

或做交互式 rebase 删除要删除的提交(commit)里对应的行。

10.1.7 修正后的提交推到远程,但报错:
To https://github.com/yourusername/repo.git
! [rejected]        mybranch -> mybranch (non-fast-forward)
error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

注意, rebasing(见下面)和修正(amending)会用新提交代替旧的, 所以如推过一次修正前的提交(commit),那现在必须强推(force push) (-f)。注意 – 总是确保你指明的分支!

(my-branch)$ git push origin mybranch -f

避免强推. 最好创建和推送新提交,而不是强推修正后的提交。后者使那些与该分支或该分支的子分支工作的开发者,在源历史中产生冲突。

10.1.8 意外做了硬重置(hard reset),想找回内容

如做了 git reset --hard, 也能找回提交(commit), 因 Git 对每件事都有日志,且存几天

(master)$ git reflog

看到过去提交(commit)列表, 和一个重置提交。选择要回退的 SHA,重置:

(master)$ git reset --hard SHA1234
10.1.9 重置个不想保留的提交,但是现在又想要回滚?
# 获取所有操作历史
git reflog
# 重置到相应提交
git reset HEAD@{4}
# 或
git reset --hard <提交的哈希值>
10.1.10 清理本地仓库?
git fetch origingit checkout mastergit reset --hard origin/maste

10.2 暂存(Staging)

10.2.1 把暂存的内容加到上次提交
(my-branch*)$ git commit --amend
10.2.2 暂存部分新文件,而不是全部

如果想暂存文件的一部分, 可这样做:

$ git add --patch filename.x

-p 简写。打开交互模式, 用 s 选项分隔提交(commit);然如果这个文件是新的, 没有该选择,添加个新文件时, 这样做:

$ git add -N filename.x

然后, 用 e 选项来手动选择要添加的行,执行 git diff --cached 会显示哪些行暂存了哪些行只是保存在本地了。

10.2.3 把一个文件的变化加到两个提交

git add 把整个文件加入到提交. git add -p 交互式选择要提交的部分.

10.2.4 把暂存的变成未暂存,把未暂存的暂存

先 stash 未暂存内容, 再重置(reset),再 pop 第一步 stashed 内容, 最后 add

$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A

10.3 未暂存的内容

10.3.1 把未暂存内容移到新分支
$ git checkout -b my-branch
10.3.2 把未暂存内容移到另个分支
$ git stash
$ git checkout my-branch
$ git stash pop
10.3.3 丢弃本地未提交的变化

如果只想重置源(origin)和本地(local)间的一些提交(commit),可以:

# one commit
(my-branch)$ git reset --hard HEAD^
# two commits
(my-branch)$ git reset --hard HEAD^^
# four commits
(my-branch)$ git reset --hard HEAD~4
# or
(master)$ git checkout -f

重置某个特殊的文件, 可用文件名做为参数:

$ git reset filename
10.3.4 丢弃某些未暂存内容

想丢弃工作拷贝中的部分内容,签出(checkout)不要的内容,保留需要的。

$ git checkout -p
# Answer y to all of the snippets you want to drop

另个方法是 stash, Stash 要保留的内容, 重置工作拷贝, 重新应用保留的部分。

$ git stash -p
# Select all of the snippets you want to save
$ git reset --hard
$ git stash pop

或stash 不要的部分, 然后 stash drop。

$ git stash -p
# Select all of the snippets you don't want to save
$ git stash drop

10.4 分支

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

这是另种 git reflog 情况,找到这次错误拉(pull) 前的 HEAD 指向。

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

重置分支到所需的提交(desired commit):

$ git reset --hard c5bc55a
10.4.2 扔掉本地提交,使分支与远程保持一致

先确认没推(push)内容到远程。

git status 会显示领先(ahead)源(origin)多少个提交:

(my-branch)$ git status
# On branch my-branch
# Your branch is ahead of 'origin/my-branch' by 2 commits.
#   (use "git push" to publish your local commits)

方法:

(master)$ git reset --hard origin/my-branch
10.4.3 新分支错提交到master

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

(master)$ git branch my-branch

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

(master)$ git reset --hard HEAD^

HEAD^HEAD^1 简写,可通过指定设置的HEAD来重置。

或不用 HEAD^, 找到重置到的提交(commit)的 hash(git log 能够完成), 然后重置到 hash。使用git push 同步内容到远程。

如, master 分支想重置到的提交的 hash 为a13b85e:

(master)$ git reset --hard a13b85e
HEAD is now at a13b85e

签出(checkout)刚才新建的分支继续工作:

(master)$ git checkout my-branch
10.4.4 保留另个 ref-ish 的整个文件

假设正在做原型方案, 有成百的内容,每个都工作得很好。现在, 提交到一个分支,保存工作内容:

(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."

当想把它放到一个分支里 (featuredevelop), 你关心是保持整个文件的完整,你想要一个大的提交分隔成比较小。

假设有:

  • 分支 solution, 拥有原型方案, 领先 develop 分支。
  • 分支 develop, 在这里你应用原型方案的一些内容。

可通过把内容拿到你的分支里,解决这个问题:

(develop)$ git checkout solution -- file1.txt

这会把这个文件内容从分支 solution 拿到分支 develop 里来:

# On branch develop
# Your branch is up-to-date with 'origin/develop'.
# Changes to be committed:
#  (use "git reset HEAD ..." to unstage)
#
#        modified:   file1.txt

然后, 正常提交。

10.4.5 把几个提交提交到一个分支,而这些提交应分布在不同分支

假设有个master分支, 执行git log, 看到做过两次提交:

(master)$ git log

commit e3851e817c451cc36f2e6f3049db528415e3c114
Author: Alex Lee <[email protected]>
Date:   Tue Jul 22 15:39:27 2014 -0400

    Bug #21 - Added CSRF protection

commit 5ea51731d150f7ddc4a365437931cd8be3bf3131
Author: Alex Lee <[email protected]>
Date:   Tue Jul 22 15:39:12 2014 -0400

    Bug #14 - Fixed spacing on title

commit a13b85e984171c6e2a1729bb061994525f626d14
Author: Aki Rose <[email protected]>
Date:   Tue Jul 21 01:12:48 2014 -0400

    First commit

用提交 hash(commit hash)标记 bug (e3851e8 for #21, 5ea5173 for #14).

首先, 我们把master分支重置到正确的提交(a13b85e):

(master)$ git reset --hard a13b85e
HEAD is now at a13b85e

现在, 我们对 bug #21 创建一个新的分支:

(master)$ git checkout -b 21
(21)$

接着, 用 cherry-pick 把对 bug #21 提交放入当前分支。即将应用(apply)这个提交(commit),仅这一个提交(commit),直接在 HEAD 上面。

(21)$ git cherry-pick e3851e8

这时这里可能会产生冲突, 参见交互式 rebasing 章 冲突节 解决冲突.

再者为 bug #14 创建个新分支, 也基于master分支

(21)$ git checkout master
(master)$ git checkout -b 14
(14)$

最后, 为 bug #14 执行 cherry-pick:

(14)$ git cherry-pick 5ea5173
10.4.6 删除上游(upstream)分支被删除了的本地分支

一旦在 github 合并(merge)一个 pull request, 就可删除 fork 里被合并的分支。如果不继续在这个分支里工作, 删除这个分支的本地拷贝会更干净,使你不会陷入工作分支和一堆陈旧分支的混乱之中。

$ git fetch -p
10.4.7 不小心删除了我的分支

如定期推送到远程, 多数情况下是安全的,但有时可能删了没推远程的分支。先创建个分支和一个新文件:

(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
(my-branch)$ git log

commit 4e3cd85a670ced7cc17a2b5d8d3d809ac88d5012
Author: siemiatj <[email protected]>
Date:   Wed Jul 30 00:34:10 2014 +0200

    foo.txt added

commit 69204cdf0acbab201619d95ad8295928e7f411d5
Author: Kate Hudson <[email protected]>
Date:   Tue Jul 29 13:14:46 2014 -0400

    Fixes #6: Force pushing after amending commits

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

(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)里所有动作的历史。

(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),看看是否能恢复删除的分支。

(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

把删除的文件找回来了。Git 的 reflog 在 rebasing 出错时也同样有用。

10.4.8 删除一个分支

删除个远程分支:

(master)$ git push origin --delete my-branch

(master)$ git push origin :my-branch

删除一个本地分支:

(master)$ git branch -D my-branch
10.4.9 从别人正在工作的远程分支签出(checkout)个分支

首先, 从远程拉取(fetch) 所有分支:

(master)$ git fetch --all

从远程 daves分支签出到本地 daves

(master)$ git checkout --track origin/daves
Branch daves set up to track remote branch daves from origin.
Switched to a new branch 'daves'

(--trackgit checkout -b [branch] [remotename]/[branch] 的简写)

这样就得到 daves 分支的本地拷贝, 任何推过(pushed)的更新,远程都能看到.

10.5 Rebasing 和合并(Merging)

10.5.1 撤销 rebase/merge

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

(my-branch)$ git reset --hard ORIG_HEAD
10.5.2 已 rebase 过, 但是我不想强推(force push)

如果想把这些变化(changes)反应到远程分支,就必须强推(force push)。是因你快进(Fast forward)提交,改变 Git 历史, 远程分支不接受变化(changes),除非强推(force push)。这就是许多人用 merge 而舍弃 rebasing 工作流的主原因之一, 强推(force push)会使大的团队陷入麻烦。注意,安全使用 rebase 的方法是,不把你的变化(changes)反映到远程分支上, 而是按下面的做:

(master)$ git checkout my-branch
(my-branch)$ git rebase -i master
(my-branch)$ git checkout master
(master)$ git merge --ff-only my-branch

更多, 参见 this SO thread.

http://stackoverflow.com/questions/11058312/how-can-i-use-git-rebase-without-requiring-a-forced-push

10.5.3 组合(combine)几个提交(commit)

假设工作分支将会做对于 master 的 pull-request。一般不关心提交(commit)的时间戳,只想组合 所有提交(commit) 到一个单独的里面, 然后重置(reset)重提交(recommit)。确保主(master)分支是最新的和你的变化都已提交了, 然后:

(my-branch)$ git reset --soft master
(my-branch)$ git commit -am "New awesome feature"

如果要更多的控制, 想保留时间戳, 需做交互式 rebase (interactive rebase):

(my-branch)$ git rebase -i master

如果没有其它分支, 将相对自己的HEAD 进行 rebase。如:组合最近两次提交(commit), 你将相对于HEAD\~2 进行 rebase, 组合最近 3 次提交(commit), 相对于HEAD\~3, 等。

(master)$ git rebase -i HEAD~2

在执行了交互式 rebase 的命令(interactive rebase command)后, 你将在你的编辑器里看到类似下面内容:

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
pick b729ad5 fixup
pick e3851e8 another fix

# Rebase 8074d12..b729ad5 onto 8074d12
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

所有以 # 开头行都是注释, 不影响 rebase.

然后,可用任何上面命令替换 pick, 也可通过删除对应行删除提交(commit)。

如果想单独保留最旧(first)提交(commit),组合所有剩下的到第二个里面, 应编辑第二个提交(commit)后的每个提交(commit) 前的单词为 f:

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
f b729ad5 fixup
f e3851e8 another fix

如果想组合这些提交(commit) 并重命名这个提交(commit), 应在第二个提交(commit)旁边添加个r,或更简单的用s 替代 f:

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
s b729ad5 fixup
s e3851e8 another fix

你可在接下来弹出的文本提示框里重命名提交(commit)。

Newer, awesomer features

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 8074d12
# You are currently editing a commit while rebasing branch 'master' on '8074d12'.
#
# Changes to be committed:
#    modified:   README.md
#

如果成功, 应看到类似下面内容:

(master)$ Successfully rebased and updated refs/heads/master.
10.5.3.1 安全合并(merging)策略

--no-commit 执行合并(merge)但不自动提交, 给用户在做提交前检查和修改机会。 no-ff 会为特性分支(feature branch)的存在过留下证据, 保持项目历史一致。

(master)$ git merge --no-ff --no-commit my-branch
10.5.3.2 需将一个分支合并成一个提交(commit)
(master)$ git merge --squash my-branch
10.5.3.3 只想组合(combine)未推的提交(unpushed commit)

有时在将数据推向上游前,你有几个正进行的工作提交(commit)。这时不希望把已推(push)过的组合进来,因为其他人可能已有提交(commit)引用它们了。

(master)$ git rebase -i @{u}

这会产生一次交互式的 rebase(interactive rebase), 只会列出没有推(push)的提交(commit), 在这个列表时进行 reorder/fix/squash 都是安全的。

10.5.4 检查是否分支上的所有提交(commit)都合并(merge)过

检查一个分支上的所有提交(commit)是否都已合并(merge)到其它分支, 你应在这些分支的 head(或任何 commits)间做一次 diff:

(master)$ git log --graph --left-right --cherry-pick --oneline HEAD...feature/120-on-scroll

这会告诉你在一个分支里有而另个分支没有的所有提交(commit), 和分支间不共享的提交(commit)列表。另一个做法是:

(master)$ git log master ^feature/120-on-scroll --no-merges
10.5.5 交互式 rebase(interactive rebase)可能出现问题
10.5.5.1 这个 rebase 编辑屏幕出现’noop’

如果看到这样:

noop

意味着你 rebase 的分支和当前分支在同个提交(commit)上, 或领先(ahead)当前分支。可尝试:

  • 检查确保主(master)分支没有问题
  • rebase HEAD~2 或者更早
10.5.5.2 有冲突的情况

如果不能成功的完成 rebase, 你可能必须要解决冲突。

首先执行 git status 找出哪些文件有冲突:

(my-branch)$ git status
On branch my-branch
Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)
    modified:   README.md

在这个例子里, README.md 有冲突。打开这个文件找到类似下面的内容:

<<<<<<< HEAD
local code
=========
master code
>>>>>>> new-commit

需解决新提交代码(示例, 从中间==new-commit地方)与HEAD 不一样的地方.

有时这些合并非常复杂,应使用可视化的差异编辑器(visual diff editor):

(master*)$ git mergetool -t opendiff

在你解决完所有冲突和测试过后, git add 变化了的(changed)文件, 然后用git rebase --continue 继续 rebase。

(my-branch)$ git add README.md
(my-branch)$ git rebase --continue

如果解决完所有冲突过后,得到与提交前一样的结果, 执行git rebase --skip

任何时候想结束整个 rebase 过程,回 rebase 前的分支状态, 你可做:

(my-branch)$ git rebase --abort

10.6 杂项(Miscellaneous Objects)

10.6.1 克隆所有子模块
$ git clone --recursive git://github.com/foo/bar.git

如果已克隆:

$ git submodule update --init --recursive
10.6.2 删除标签(tag)
$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>
10.6.3 恢复已删除标签(tag)

如果想恢复个已删除标签(tag), 可按下面步骤:

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

$ git fsck --unreachable | grep tag

记下这个标签(tag)的 hash,然后用 Git 的 update-ref:

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

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

10.6.4 已删除补丁(patch)

如果某人在 GitHub 上给你发个 pull request, 然后他删除自己的原始 fork, 你将没法克隆他们的提交(commit)或用 git am。这时, 最好手动的查看他们的提交(commit),并把它们拷贝到一个本地新分支,然后做提交。

做完提交后, 再修改作者,参见变更作者。然后应用变化, 再发起新 pull request。

10.7 跟踪文件(Tracking Files)

10.7.1 只想改变一个文件名大小写,而不修改内容
(master)$ git mv --force myfile MyFile
10.7.2 想从 Git 删除个文件,但保留该文件
(master)$ git rm --cached log.txt

10.8 配置(Configuration)

10.8.1 给 Git 命令添加别名(alias)

在 OS X 和 Linux 下, Git 配置文件存在 \~/.gitconfig。在[alias] 部分添加些快捷别名(容易拼写错误的),如下:

[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
10.8.2 想缓存个仓库(repository)的用户名和密码

你可能有一个仓库需授权,这时可缓存用户名和密码,而不用每次推/拉(push/pull)时都输入,Credential helper 能帮你。

$ git config --global credential.helper cache
## Set git to use the credential memory cache
$ git config --global credential.helper 'cache --timeout=3600'
## Set the cache to timeout after 1 hour (setting is in seconds)

10.9 不知道做错了些什么

重置(reset) 些东西, 或合并了错误分支, 或强推后找不到自己的提交(commit),但想回到以前状态。

这就是 git reflog 目的, reflog 记录对分支顶端(the tip of a branch)的任何改变, 即使那个顶端没有被任何分支或标签引用。基本每次 HEAD 改变, 一条新的记录就会增加到reflog。这只对本地分支起作用,且它只跟踪动作 (如,不跟踪一个没被记录的文件的任何改变)。

(master)$ git reflog
0a2e358 HEAD@{0}: reset: moving to HEAD\~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to master
c10f740 HEAD@{2}: checkout: moving from master to 2.2

上面的 reflog 展示从 master 分支签出(checkout)到 2.2 分支,然后再签回。还有个硬重置(hard reset)到一个较旧的提交。最新动作出现在最上面以 HEAD@{0}标识.

如果事实证明不小心回移(move back)了提交(commit), reflog 会包含不小心回移前 master 上指向的提交(0254ea7)。

$ git reset --hard 0254ea7

然后使用 git reset 就可把 master 改回到之前的 commit,提供了一个在历史被意外更改情况下的安全网。

十一、小结

最后,放一张总结的脑图总结一下以上的知识点。

git 入门到精通_第13张图片

你可能感兴趣的:(git)