工作区。新文件没被add到暂存区,显红色。
暂存区。进仓库前暂时存放区域,未对本地仓库生效。对暂存区文件修改,显蓝色。第一次创建并add到暂存区的文件,由于远程仓库没同步,显绿色。(注:存放在 “.git/index目录” )
在 GIt 中,文件会处于如下三种状态:
已修改(modified) - 修改了文件,但没存到本地仓库。
已暂存(staged) - 对已修改文件版本做标记,包含在下次提交的快照中。
已提交(committed) - 数据已安全存在本地仓库中。
然后查看下.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
git Object三种类型:Blob、Tree、Commit
文件为Blob类型,文件夹为Tree类型,提交节点为Commit类型。Git以这三种类型存储文件。目录存储的映射关系:
猜想:把文件提到代码库,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
仔细想是两个 tree 是 git 根目录和 test 目录。
结论:每次 commit,都生成与对应的 commit hash 值。
git init
# 或
git clone ssh://[email protected]/repo.git
git 配置文件存在三个位置:
/etc/gitconfig
: 含每个用户及仓库配置。用 git config --system
时,会从此文件读写配置变量。~/.gitconfig
或 ~/.config/git/config
:只对当前用户。 --global
选项让 Git 读写此文件。.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
Git
服务大部分用 SSH
公钥认证,默认 SSH
密钥存在 ~/.ssh
目录。 进该目录查看密钥:
ls ~/.ssh
authorized_keys2 id_dsa known_hosts
config id_dsa.pub
如有id_dsa
、id_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),然后它要求输入两次密钥口令。如果不想用密钥时输入口令,可留空。
# 创建别名
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
.gitignore 忽略文件或目录,不纳入版本控制。
推荐 常用模板,如:Java、Nodejs、C++ 的模板等。
git add [-u][-p file] .|filename
-u 添加被跟踪的的变化到暂存区
-p 添加目录内到某些改动到暂存区
git status
-sb ,-s 短显示,-b 分支
–show-stash
注意:执行 git status
和 git branch -avv
查看仓库状态和分支状态
git clean -nfd
-n 预览删掉的文件,但不执行删除操作(强烈推荐)
-f 强删未 untracked 文件(含.gitignore但不含.gitignore规则文件/目录),无法恢复
-d 删掉未 untracked 工作区目录
-x 删除包含在.gitignore里的规则文件/目录
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
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
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
git push #数据从版本库中发送到远程仓库
git pull #从远程代码库拉取到本地仓库
git fetch origin
git checkout master
git reset --hard origin/master
git reset [--soft|--mixed|--hard] 版本库ID
–soft 仅撤销版本库,不改暂存区和工作区
–mixed 仅撤销交版本库和暂存区,不改工作区
–hard 将工作区、暂存区和版本库恢复指定版本
git diff # 工作区被跟踪与暂存区对比
git diff head # 工作区被跟踪与版本库对比
git diff --cached # 暂存区与版本库对比
git diff
终端使用不频繁,除少量改动。倾向 GUI 工具更直观。
注:commit后才算历史版本,仅add不算
# 从最新提交开始,显示所有提交记录(hash,作者信息,标题和时间)
git log
# 等同 git log --pretty=oneline --abbrev-commitgit log --oneline
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'
# 通过 commit 信息查找 (所有分支)
git log --all --grep=''
# 通过 commit 信息查找 (包含 reflog)
git log -g --grep=''
# 通过更新的内容查找
git log -S ''
# 通过日期范围查找
git log --after='DEC 15 2019' --until='JAN 10 2020'
git reflog 记录所有行为,含 rebase、merge、reset
当不小心硬回滚或变基错误,可找到 commit 然后回滚。仅本地有用。
git reflog
## reflog 查看本地以往版本号
git reflog -5
# 从当前目录文件中查找文本内容
git grep "Hello"
# 在某一版本中搜索文本
git grep "Hello" v2.5
在本地 master 上。通过 git branch
创建其他分支。
注:本地分支未被跟踪的文件,所有分支均可看到该文件,且其他分支都可修改提交,但commit后就只能由commit分支查看
master 分支需先 commit 才能生成 master 分支,否则报错:fatal: Not a valid object name: 'master'.
git branch feature/dev
git branch [-r][-a]
-r 查看远程分支
-a 查看全部分支,白色表本地有,红色仅远程存在
修改分支名称
git branch [-M old new]
git branch [-d branchName]
# 删除远程仓库不存在的分支
git fetch -p
# 移除所有包含 `greenkeeper` 的分支
git fetch -p && git branch --remote | fgrep greenkeeper | sed 's/^.\{9\}//' | xargs git push origin --delete
git branch --set-upstream-to=origin/xxx
git branch --set-upstream-to origin xxx
## 查看 当前分支与 master 的不同
git diff master..my-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节点。谁建的分支,就沿用谁指针。
注:未被放入代码库的文件在分支切换时被抛弃,造成严重后果。
分支合并有:merge 和 rebase 两种。两者都从一个分支获取并合到当前分支。
merge:自动建新 commit,如冲突则修改后重 commit。
每次都记录详细的 commit,但 commit 频繁时,分支较乱。
rebase:找公共节点,直接合并前 commit 历史,也叫变基。得到简洁分支历史,去掉 merge commit。但如果合并出问题,无痕迹,不好定位。
例1:引用网上归纳的git rebase工作流:
git rebase while(存在冲突) {
# 找到当前冲突文件,编辑解决冲突
git status # 显示工作路径下已修改的文件
git add -u
git rebase --continueif( git rebase --abort )
break;
}
注:最好不在公共分支 rebase,如果前后基本没人动分支,推荐rebase。
例2:想要 rebase 最近 3 个提交:
pick 64d26a1
feat: add index.jss 45f0259
fix: update index.jss 8b15b0a
fix: typo in index.js
修正:想在提交 fed14a4c 加上些内容。
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"
# 将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 # 推到远程
从多个分支挑取部分 commit 合并到同个地方。
类似变基,挑的 commit 若无冲突则追加。有就中断,解决后 --continue
git cherry-pick commit-sha1
git cherry-pick master~4 master~2
git cherry-pick startGitSha1..endGitSha1
在合并时产生。分支合并是 tree 和 tree 合并。执行 git merge master 时。git 先找到两分支从哪个指针创建,称“merge base”。然后检查两次 tree 是否一致,如不一致说明文件被修改。接下来就某文件分类讨论:
解决冲突
<<<<<<< HEADCreating a new branch is quick & simple.=======Creating a new branch is quick AND simple.>>>>>>> feature1
在 <<<<<<<,=======
为自己;=======,>>>>>>>为别人。此时需与开发者商定,解决冲突。如保留自己代码,删掉别人代码。
常从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
git checkout -- a.html
已 git add
但未 git commit
,执行操作后,恢复到git add
后状态
注:一旦执行 git commit -m "*"
,就不能用上面回退。
版本回退离不开reset和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 文案被废弃。
然后考虑 reset , reset 也能回到某次提交,但与 revert 不同, reset 是将提交的 HEAD 指针指到某次提交,之后提交记录会消失。
由于在 feature 分支开发,在 feature 分支将代码回退到某版本后,其合到 develop 分支时却报错。因为 feature 分支回退提交后,在 git 的 workflow 里,feature 分支落后于 develop 分支,而合并向 develop 分支,又要和 develop 分支保持最新同步,需将 develop 分支合并到 feature 分支,而合并后,原被 reset 的代码又回来了。
这时另个可选项是在 master 上执行 reset,用 —hard 选项完全抛弃旧代码,reset 后再强制推到远端。
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差异放到工作区,然后清空暂存区。执行后只要有差异文件就变红色,难以区分。
# 获取所有操作历史
git reflog
# 重置到相应提交
git reset HEAD@{4}
#或
git reset --hard <提交的哈希值>
# 第一步:本地回退到指定的版本
git reset --hard 版本号
# 第二步:将远程的也回退到指定版本
git push -f origin dev
当前分支切到其他分支而不想 commit 时,可用 git stash
将数据都暂存Git栈。(注:仅暂存被跟踪文件,用前应先提交到本地,再 stash)
将当前更改的代码储藏,以后恢复使用
git stash # 保存所有正在追踪的文件
git stash save "日志信息"
git stash -u save stashName #-u 包含未被追踪的文件
git stash push -m "更改了 xx"
查看本地所有 stash
git stash list
# 恢复同时把 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。
# 获取并删除暂存项
git stash apply stash@{1}
git stash drop stash@{1}
# 或用一条命令
git stash pop stash@{1}
# 将stash空间清空
git stash clear
git stash show stash@{1}
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
git tag v1.0.0
git show v1.0.0
git tag -a v1.0.0 -m "version 1.0.0 released" 34372b05(commit版本号)
# 查看标签详细信息
-a指定标签名
-m指定说明文字
如周五给周一某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
git remote -v #列出所有的仓库地址
git remote show origin #查看远程仓库的信息
git remote rename pb paul #修改别名
git remote rm paul #删除别名
git remote add origin url
git remote set-url origin(或其他上游域) url
git rev-parse
快速获取部分git 仓库信息,弄脚本时用
git rev-parse --short HEAD --verify
git rev-parse --show-toplevel
git rev-parse --git-dirgit rev-parse --all
本地同步远程删除的分支
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 fetch
或 git rebase origin/master
实现 “使本地 master 分支基于远程 master 分支”,rebase
常用
git pull
大多带--rebase(-r)
(变基形式拉取合并代码),保持分支一条线。
默认pull
走ff
模式,多数情况会产生新 commit,部分参数与 merge
一致。
# 从远程更新代码到本地且合并
git pull github master
git pull --rebase origin master # 远程拉取与合并可用git pull,变基也行,变基过程可随时 abort 停止。简写 git pull -r origin master
# 将本地版本推送到远程端
$ 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
查远程有但本地无的分支:git branch -a
删掉没与远程分支对应的本地分支:git fetch -p
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
许多 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
。
修改仓历史不仅限于修改上次提交信息,用 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
温馨提示:
可用不同格式(zip
或tar
)压缩特定引用的跟踪文件:
git archive --format <format> --output <filename> <ref>
可是个分支 commit hash 或标签。 https://git-scm.com/docs/git-archive
有快捷方式可表示刚用的分支:一个单破折号-
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 Flow
实际开发中的最佳实践策略 Git Flow 可归纳为:
先说系统开发过程中常用的环境。
简称 | 全称 |
---|---|
DEV | Development environment |
FAT | Feature Acceptance Test environment |
UAT | User Acceptance Test environment |
PRO | Production environment |
如项目域名:http://www.abc.com
,那相关环境的域名配置如下:
http://fat.abc.com
http://uat.abc.com
http://www.abc.com
接下来,针对不同的环境来设计分支。
分支 | 名称 | 环境 | 可访问 |
---|---|---|---|
master | 主分支 | PRO | 是 |
release | 预上线分支 | UAT | 是 |
hotfix | 紧急修复分支 | DEV | 否 |
develop | 测试分支 | FAT | 是 |
feature | 需求开发分支 | DEV | 否 |
master
为主分支,用于部署正式环境(PRO),由 release
或 hotfix
分支合并,任何情况不许在 master 分支改代码。
release
为预上线分支,用于部署预上线环境(UAT),保持与 master
一致,一般由 develop
或 hotfix
分支合并,不建议在 release
分支修改代码。
如果在 release
分支测出问题,需回归验证 develop
分支看否存在此问题。
hotfix
为紧急修复分支,命名规则为 hotfix-
开头。
当线上出现紧急问题时,需基于 release
或 master
分支创建 hotfix
分支,修完后,再合并到 release
或 develop
分支,上线后便将其删除。
develop
为测试分支,用于部署到测试环境(FAT),始终保持最新完成及 bug 修复后的代码,可根据需求大小确定是由 feature
分支合并,还是直接在上面开发。满足测试的代码才能往上面合并或提交。
feature
需求开发分支,规则为 feature-
开头,一旦需求上线,便将其删除。接下来举几个开发场景。
有订单管理需求,先建 feature-order
分支。该分支基于哪个分支创建?如有 未测完的需求,就基于 master
创建。如没有 未测试完毕 需求,就基于 develop
创建。
feature-order
分支开发完,提测时先确定 develop
不存在未测试完的需求,这时研发才能将代码合并到 develop
(测试环境)供测试者测试。develop
(测试环境) 测试通过后,研发再将代码发到 release
(预上线环境)供测试人员测试。release
(预上线环境)测试通过后,研发再将代码发布到 master
(正式环境)供测试人员测试。master
(正式环境) 测试通过后,研发需删除 feature-order
分支。有个订单管理的迭代需求,如果开发工时 < 1d,直接在 develop
开发,如果开发工时 > 1d,那就需创建分支,在分支上开发。
开发后的提测上线流程 与 新需求加入流程一致。
在 develop
测试出 Bug,如果修复工时 < 2h,直接在 develop
修复,如果修复工时 > 2h,需在分支上修复。
修复后的提测上线流程 与 新需求加入流程一致。
在 release
测出Bug,首先想 develop
分支是否同样存在该问题。
如存在,修复流程 与 修复测试环境 Bug流程一致。
如不存在,这种可能性较少,大部分是数据兼容问题,环境配置问题等。
在 master
测出Bug,首先想 release
和 develop
分支是否存在同样问题。
如存在,修复流程 与 修复测试环境 Bug流程一致。
如不存在,这种可能性也较少,大部分是数据兼容问题,环境配置问题等。
需求在测试环节未测出 Bug,上线运行后出现 Bug,需紧急修复。
个人理解紧急修复即没时间验证测试环境,但建议验证下预上线环境。
release
分支有 未测完需求,就基于 master
建 hotfix-xxx
分支,修复完发 master
验证,验完后将 master
合到 release
和 develop
分支,同时删 hotfix-xxx
分支。release
分支无 未测完需求,但 develop
分支存有 测完需求,就基于 release
建 hotfix-xxx
分支,修复完后发 release
验证,后面流程与上线流程一致,验完后将 master
代码合到 develop
分支,同时删掉 hotfix-xxx
分支。release
和 develop
分支无 未测试完需求, 就在 develop
分支修复完后,发到 release
验证,后面流程与上线流程一致。在一个项目中并行开发两个需求,并行提测,但上线日期不同。
两种方案:
git cherry-pick
“挑拣”提交建议参考规范:type(scope):subject
比如:fix(首页模块):修复弹窗 JS Bug。
type
表示 动作类型,可分为:
scope
表 影响范围,分为:模块、类库、方法等。
subject
表 简短描述,最好不超 60 字,如有关 Bug 的 Jira 号,建议在描述中添加。
较流行方案是 约定式提交规范(Conventional Commits),它受 Angular 提交准则启发,并在很大程度上以其为依据。约定式提交规范是种基于提交消息的轻量级约定。它提供一组用于创建清晰的提交历史的简单规则;这使编写基于规范的自动化工具变得更容易。这个约定与 SemVer 吻合,在提交信息中描述新特性、bug 修复和破坏性变更。它的 message 格式如下:
<类型>[可选的作用域]: <描述>
[可选的正文]
[可选的脚注]
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。
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 只能做格式规范,无法触及内容。对内容质量的把控只能靠自己。
创建 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" }}
执行 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
。
针对团队使用情况,拟定 commit message
每部分的填写规则。
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
。
scope
必填,描述改动范围,格式:项目名/模块名,如:node-pc/common
rrd-h5/activity
,而we-sdk
不需指定模块名。如一次 commit 修改多个模块,建议拆成多次commit,以便更好追踪和维护。
body
详细描述,主要描述改前的情况及动机,小修改不作要求,但重大需求、更新等须加 body 说明。
break changes
指明是否产生破坏性修改,涉及 break changes 改动须指明该项,类似版本升级、接口参数减少、接口删除、迁移等。
affect issues
指明是否影响某问题。如用 jira 时,在 commit message
中可写影响的JIRA_ID
,若开启该功能需先打通jira
与gitlab
。参考:docs.gitlab.com/ee/user/pro…
填写方式例如:
re #JIRA_IDfix #JIRA_ID
不确定提交了什么时,用如下命令显示最近一次提交(commit):
(master)$ git show
# 或
$ git log -n1 -p
如提交未推, 可用如下方法修改提交信息
$ git commit --amend
打开默认编辑器编辑. 也可用一条命令一次完成:
$ git commit --amend -m 'xxxxxxx'
如提交已推, 可修改这次提交(commit)然后强推(force push), 但不推荐。
如果是单个提交(commit),修改它:
git commit --amend --author "New Authorname "
如要修改所有历史, 参考 git filter-branch
指南页.
从提交(commit)里移除个文件:
$ git checkout HEAD^ myfile
$ git add -A
$ git commit --amend
非常有用,当有个开放补丁(open patch),往上面提交不必要的文件,需强推(force push)去更新这个远程补丁。
如要删除已推提交(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
;更多 参考。
警告:万不得已时才如下做.
git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
git push -f [remote] [branch]
或做交互式 rebase 删除要删除的提交(commit)里对应的行。
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
避免强推. 最好创建和推送新提交,而不是强推修正后的提交。后者使那些与该分支或该分支的子分支工作的开发者,在源历史中产生冲突。
如做了 git reset --hard
, 也能找回提交(commit), 因 Git 对每件事都有日志,且存几天
(master)$ git reflog
看到过去提交(commit)列表, 和一个重置提交。选择要回退的 SHA,重置:
(master)$ git reset --hard SHA1234
# 获取所有操作历史
git reflog
# 重置到相应提交
git reset HEAD@{4}
# 或
git reset --hard <提交的哈希值>
git fetch origingit checkout mastergit reset --hard origin/maste
(my-branch*)$ git commit --amend
如果想暂存文件的一部分, 可这样做:
$ git add --patch filename.x
-p
简写。打开交互模式, 用 s
选项分隔提交(commit);然如果这个文件是新的, 没有该选择,添加个新文件时, 这样做:
$ git add -N filename.x
然后, 用 e
选项来手动选择要添加的行,执行 git diff --cached
会显示哪些行暂存了哪些行只是保存在本地了。
git add
把整个文件加入到提交. git add -p
交互式选择要提交的部分.
先 stash 未暂存内容, 再重置(reset),再 pop 第一步 stashed 内容, 最后 add
$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A
$ git checkout -b my-branch
$ git stash
$ git checkout my-branch
$ git stash pop
如果只想重置源(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
想丢弃工作拷贝中的部分内容,签出(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
这是另种 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
先确认没推(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
在 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
假设正在做原型方案, 有成百的内容,每个都工作得很好。现在, 提交到一个分支,保存工作内容:
(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."
当想把它放到一个分支里 (feature
或 develop
), 你关心是保持整个文件的完整,你想要一个大的提交分隔成比较小。
假设有:
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
然后, 正常提交。
假设有个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
一旦在 github 合并(merge)一个 pull request, 就可删除 fork 里被合并的分支。如果不继续在这个分支里工作, 删除这个分支的本地拷贝会更干净,使你不会陷入工作分支和一堆陈旧分支的混乱之中。
$ git fetch -p
如定期推送到远程, 多数情况下是安全的,但有时可能删了没推远程的分支。先创建个分支和一个新文件:
(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 出错时也同样有用。
删除个远程分支:
(master)$ git push origin --delete my-branch
或
(master)$ git push origin :my-branch
删除一个本地分支:
(master)$ git branch -D my-branch
首先, 从远程拉取(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'
(--track
是 git checkout -b [branch] [remotename]/[branch]
的简写)
这样就得到 daves
分支的本地拷贝, 任何推过(pushed)的更新,远程都能看到.
可合并(merge)或 rebase 一个错误分支, 或完成一个进行中的 rebase/merge。Git 在危险操作时会把原始 HEAD 保存在个 ORIG_HEAD 的变量里, 所以要把分支恢复到 rebase/merge 前的状态是很容易的。
(my-branch)$ git reset --hard ORIG_HEAD
如果想把这些变化(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
假设工作分支将会做对于 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.
--no-commit
执行合并(merge)但不自动提交, 给用户在做提交前检查和修改机会。 no-ff
会为特性分支(feature branch)的存在过留下证据, 保持项目历史一致。
(master)$ git merge --no-ff --no-commit my-branch
(master)$ git merge --squash my-branch
有时在将数据推向上游前,你有几个正进行的工作提交(commit)。这时不希望把已推(push)过的组合进来,因为其他人可能已有提交(commit)引用它们了。
(master)$ git rebase -i @{u}
这会产生一次交互式的 rebase(interactive rebase), 只会列出没有推(push)的提交(commit), 在这个列表时进行 reorder/fix/squash 都是安全的。
检查一个分支上的所有提交(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
如果看到这样:
noop
意味着你 rebase 的分支和当前分支在同个提交(commit)上, 或领先(ahead)当前分支。可尝试:
HEAD~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
$ git clone --recursive git://github.com/foo/bar.git
如果已克隆:
$ git submodule update --init --recursive
$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>
如果想恢复个已删除标签(tag), 可按下面步骤:
首先, 找到无法访问的标签(unreachable tag):
$ git fsck --unreachable | grep tag
记下这个标签(tag)的 hash,然后用 Git 的 update-ref:
$ git update-ref refs/tags/<tag_name> <hash>
这时标签(tag)已恢复了。
如果某人在 GitHub 上给你发个 pull request, 然后他删除自己的原始 fork, 你将没法克隆他们的提交(commit)或用 git am
。这时, 最好手动的查看他们的提交(commit),并把它们拷贝到一个本地新分支,然后做提交。
做完提交后, 再修改作者,参见变更作者。然后应用变化, 再发起新 pull request。
(master)$ git mv --force myfile MyFile
(master)$ git rm --cached log.txt
在 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
你可能有一个仓库需授权,这时可缓存用户名和密码,而不用每次推/拉(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)
你 重置(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,提供了一个在历史被意外更改情况下的安全网。
最后,放一张总结的脑图总结一下以上的知识点。