git 常用命令总结

✍🏼 写于 2016年04月21日   
❗️ 注意:离本文创建时间已经过去了 天,请注意时效性

前言

经常使用git,但是一直没时间写,现在终于有时间了,所以总结一下常用命令。

正文

新建一个分支并切换到这个分支:

1
git checkout -b branch_name

切换分支:

1
git checkout branch_name

删除一个分支(需要先checkout到另一个分支):

1
git branch -d branch_name

注意,如果你在本地新建一个分支,而没有git push的话,执行下列命令删除一个分支会提示你

1
2
error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.

确定不要的话,按说明强制删除即可。

提交文件的流程是:

1
git add ==> git commit ==> git push

git add常用的有三个参数:

  1. git add -A 所有变动记录都增加到暂存区,不管是新增还是删除还是修改。
  2. git add . 文件的增加记录,修改添加到暂存区,不包括删除的文件。
  3. git add -u 文件的删除和修改记录添加到暂存区,不包括新增的文件。

人生没有后悔药,但是git有,如果不小心提交了错误的文件或者提交信息写错了,可以根据情况执行以下命令。

首先git status 查看版本状态,(我用zsh)红色文件表示未git add到暂存区,绿色文件表示已经git add到缓存区,等待git commit

这个时候如果不想git add到缓存区的话,那就git reset HEAD全部将文件重新变为未git add的原始状态,如果只是不想git add某一个文件到缓存区的话, 需要指定文件名:

1
git reset HEAD filename

有时候不但不想把修改过的文件git add到缓存区,而且想把这个文件所做的修改都忽略掉,也即回复到未修改的状态,那重新检出即可:

1
git checkout -- filename

注意这里没有拼写错误,checkoutfilename之间有左右存在一个空格的短横线。

git add之后就是git commit,可以跟一个参数-m来写下提交说明,这是个好习惯,而且是被强制的,如果没有-m参数,则会进入一个vim编辑状态,提示你加上信息:

1
git commit -m '这是一个提交说明'

这个时候发现提交说明写错了,不想commit了(此时HEAD已经变了),两个方法:

一个是移除commit:(因为这个还没有push,只是commit到本地仓库)

首先git log,记下你误commit之前的一个commit id,是一个hash(SHA),如:73cf3bfc3419a85e959d7ecfcb917d9cdc24b3c9或者直接是最近的一次pushHEAD也行:

1
git reset 73cf3bfc3419a85e959d7ecfcb917d9cdc24b3c9

(这个时候不能像git add的时候使用git reset HEAD了因为git commit之后HEAD已经变了(即已经可以通过git log查看提交记录了))

这里插一句git reset命令的三个参数:--mixed,--soft,--hard:

网上的教程一大堆,又是画图说明又是引用官方分支说明的,麻烦,我说的简单点。

简单的,举例子的方式简单点。 —Xheldon

git reset默认是mixed参数,即执行git reset xxxx(xxx表示一个SHA或者未commit的时候是HEAD) 即是执行git reset --mixed xxxx, 他的作用是将文件恢复到你修改过文件之后没有执行任何git命令的状态(文件是红色状态)。

--soft参数是将文件仅仅是恢复到未commit的状态,其文件还是git add过的(还是绿的)。

--hard就比较强势了,它会将你的文件彻底恢复到你指定的提交记录的状态,不管你是add过还是commit过还是修改过文件,统统无视。注意,执行git reset --hard xxx具有一定的危险性,会将你当前的修改从本机删除。

git reset --hard xxx之后,文件已经从本机删除了,你所有的修改也已经被删除,但是想找回hard删除的文件修改记录怎么办呢?使用git reflog

你的每一步commitreset操作,git都会生成一个记录,这个记录可以在通过git reflog找到,在每个记录之前有个短hash,复制这个短hash,重新执行一遍git reset --hard short_hash即可。

注意,执行git commit之后这个时候的HEAD已经变成你提交过的文件的更改状态,再执行git reset --mixed HEAD或者git reset --soft HEAD无效(因为当前的HEAD就是你commit之后的那个点(即使你没有push)),如果想返回到git add之后,git commit之前的状态需要git reset --soft commit_id,如果返回到git add之前的状态,需要git reset --mixed commit_id, 或者直接git reset commit_id

git commit后悔药的另一牌子叫--amend,提交之后,后悔了,发现msg写错了,或者文件又修改了,不想再生成一条commit记录因为很丑而且显得你很菜,居然会犯这种提交信息写错的低级错误,那么运行:

1
git commit --amend change_file_name_after_commit -m "新的msg"

即可。

后悔药吃过了,git commit之后确认无误就可以git push了。这个时候如果你当前分支所分出来的远程分支没有其他人提交更新的话,你就可以使用fast forward 模式,中文翻译成快进模式,直接合并进去。形象的查看合并情况可以使用:

1
git log --graph --pretty=oneline

如果在你分出分支期间,还有其他人也提交了分支,如果没有冲突的话那也可以直接合并,需要你先git pull下来,再执行git push。 如果有冲突的话这个时候会提示你别人的修改和你的修改有什么冲突,这个就需要你手动解决,解决完之后就git add, git commit即可。

合并分支:

1
git merge another_branch_name

这里的情况是非fast forward模式,即B从A分支上分出后,作为父分支的A分支又改变了,B分支这个时候也改了点东西,再想合并回A分支的时候,就出现了现在的情况以下情况: 注意,假设你在A分支上,需要merge的是B分支,则merg过来的B分支必须是git push过的,如果B分支只git commit的分只是不会被merge的。因为git merge branch_namebranch_name是从branch_name的远程originmerge的,commit只修改了本地的HEAD,没有push就没有修改远程origin。 假设B分支已经git push了自己的改动到远程,而本地A也git add了自己的改动到本地仓库,则在A分支上执行git merge B的时候会出现(假设改动的是config.js):

1
2
3
4
Updating 474cfbf..9c94d0c
error: Your local changes to the following files would be overwritten by merge:config.js
Please commit your changes or stash them before you merge.
Aborting

意思就是合并两个分支的时候出错中断。会让你先暂存你的当前分支的修改即git stash或者提交你的修改即git commit。 之后git merge之后,出现冲突再手动修改,重新提交。 OK,我们先git commit当前的修改,再次执行git merge B,这个时候出现(假设冲突文件是config.js):

1
2
3
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.

手动解决之后,再重新git addgit commit即可。

注意,如果你在一个分支上修改了文件,而在checkout到另一个分支的时候没有发生冲突,则不会有任何提示,文件改动依然存在,因此你可以将文件在一个分支上改动后,再提交到另一个分支上。而如果在一个分支上改动文件之后,再checkout其他分支出现冲突的话(比如其他分支git pull了,或者其他分支git commit了相同文件的相同修改):

1
2
3
4
error: Your local changes to the following files would be overwritten by checkout:
	config.js
Please commit your changes or stash them before you switch branches.
Aborting

这个时候就需要你先git stash储藏(此stash储藏不是git add暂存stash是先将文件放到一个特定的区域,等到切换完分支之后,再像一个补丁一样,应用这个stash到切换到的checkout分支,或者不应用stash,等切换的分上的事情做完之后,再切换回来的时候,再次应用这个stash,当然如果你一直不想应用你的stash也可以一直不应用,没有关系). 这里说到了git stash,这个命令的使用场景是同时需要做两个分支的修改的时候,其中一个分支做到了一半,这个时候另一个分支也需要修改,你不可能把已经做一半的内容丢弃,又不能commit,因为这有可能在checkout过去的会造成conflict,因此你需要储藏stash: 先查看当前状态,git status

1
2
3
4
5
6
7
8
9
On branch optimize
Your branch is up-to-date with 'origin/optimize'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   config.js

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

储存一下,git stash:

1
2
Saved working directory and index state WIP on optimize: 24bf0e1 test add
HEAD is now at 24bf0e1 这是一个提交

之后再git status看下:

1
2
3
On branch optimize
Your branch is up-to-date with 'origin/optimize'.
nothing to commit, working tree clean 

这个时候可以放心的切换到其他分支,这里要说明的是,切换到其他分支的时候你也可以使用储藏的分支到当前分支(只要你不怕冲突),查看储藏列表,git stash list:

1
stash@{0}: WIP on optimize: 24bf0e1 test add

这是刚刚stash起来的改动,可以使用git stash apply应用这个最近的改动,如果stash的改动有好几个,那就指定stash的名字:

1
git stash apply stash@{0}

应用之后就会从储存区删除这个stash,如果不希望的删除的话就:

1
git stash apply stash_name --index

之后再希望删除的话就:

1
git stash drop stash_name

以上两个命令不带stash_name的话默认删除最近的一个stash

暂时能想到的就这么多吧,如果不熟悉,还是用Sourcetree吧。

更新

git revert/reset/rebase 只看说明是搞不懂的, 需要你自己输入命令测试一下.

我就喜欢为了干净整洁的提交历史而视同 git rebase, 但是这个命令很危险, 有一些使用场景需要注意. 而比 git rebase 更危险的是 git reset, 它会把当前项目重置到某一次提交. 而 git revert 就相对安全一些, 但是你想 revert 之前的某个提交最好需要保证你的缓存区是空的, 否则会遇到错误提示.

当然, git 命令千千万(夸张), 有一些命令是针对一些特殊场景的, 在没遇到之前可能无法理解其中的一些用法, 这个很正常.

比如 git reset commitId 即是把 HEAD 移动到 commitId 所在的地方, 你可能一头雾水, 移动 HEAD 有什么用? 这个命令的目的是什么? 再比如 git revert commitId 是把 commitId 的提交给移除, 而不移动 HEAD 的指针.

看代码(d0b9def 对应 commit -m 'reset/revert test 3' 这个提交, 当前 HEAD'reset/revert test 4'上):

git revert d0b9def 之后, 你的代码可能是这个样子的:

1
2
3
4
5
6
7
reset/revert test 1
reset/revert test 2
<<<<<<< HEAD
reset/revert test 3
reset/revert test 4
=======
>>>>>>> parent of d0b9def... reset/revert test

在相同的 HEAD 上执行 git reset d0b9def 之后, 你的工作区可能是这个样子的:

1
2
3
4
 reset/revert test 1
 reset/revert test 2
 reset/revert test 3
+reset/revert test 4

看出移动不移动 HEAD 的区别了吧? revert 一定会让你手动解决冲突, 因为其保留的是从你 commitId 之前的一个父 commit 到当前 HEAD 的除了 d0b9def 的所有变动. 而 reset 不会让你解决冲突, 而是默默的移动 HEAD 把自 d0b9def 以来所有的变动都显示为文件改动, 需要你手动 git add/commit 一下, 当然少不了 push --force.

因此, revert 被设计为撤销公开的提交的安全方式,reset 被设计为重设本地更改。因为两个命令的目的不同,它们的实现也不一样:重设完全地移除了一堆更改,而撤销保留了原来的更改,用一个新的提交来实现撤销。

- EOF -
本文最先发布在: git 常用命令总结 - Xheldon Blog