Git常用命令手册
阅读前说明
本手册总结了Git的常用命令,目的是方便平时使用Git可视化工具管理代码的同事学习和查询命令行下Git的命令,一些不常用的高级用法并没有写入
符号说明
- 中括号 []
本文中使用中括号 [] 表示要自定义输入的内容,例如git add [test.text]表示在命令行中输入git add test.text
Git基础操作
Git仓库创建
有两种创建Git项目仓库的方法,第一种是在现存的目录下,通过导入所有文件来创建新的 Git 仓库。第二种是从已有的 Git 仓库克隆出一个新的镜像仓库。
- 在已有的工作目录中创建Git仓库:
打开命令行,输入
cd [需要创建Git仓库的文件夹路径]
移动到需要使用Git管理的目录下,可以直接将文件夹拖入命令行窗口快速输入路径,然后再输入
git init
在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。不过目前,仅仅是按照既有的结构框架初始化好了里边所有的文件和目录,但我们还没有开始跟踪管理项目中的任何一个文件。
如果当前目录下有几个文件想要纳入版本控制,需要先用
git add [fileName]
命令告诉 Git 开始对名字为fileName文件进行跟踪,也可以输入
git add .
对该文件夹下的所有文件进行跟踪
然后
git commit -m [initial project version]
提交这次改动,所有被跟踪的文件就被记录进Git的数据库中。
- 从现有仓库克隆:
假设某项目在GitHub上的仓库地址为URL,输入
git clone [URL]
如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字
git clone [URL] [myProject]
新建的目录名字会变成myProject。
Git 支持多种数据传输协议。之上面的例子使用的是 git:// 协议,也可以用 http(s):// 或者 user@server:/path.git 表示的 SSH 传输协议。
Git的工作流程
上一步我们已经创建了一个Git仓库,并将一些文件加入了跟踪列表,在进行下一步操作之前,我们有必要了解一下Git的工作流程:
我们创建Git仓库的目录叫做工作目录,工作目录下面的所有文件处于两种状态:已跟踪或未跟踪。已跟踪的文件指使用git add命令加入版本控制管理的文件,工作一段时间后,它们的状态可能是未更新,已修改或者已放入暂存区。而没有加版本控制管理入都属于未跟踪文件。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。
在编辑过某些文件之后,Git将这些文件标为已修改。我们可以把这些修改过的文件放到暂存区域,当我们使用git commit命令后暂存区的文件会被保存进数据库,状态变为未修改,如此重复。
检查当前文件状态
要确定哪些文件当前处于什么状态,可以用:
git status
如果是刚创建的仓库,则会显示
On branch master
nothing to commit, working directory clean
这说明现在的工作目录是干净的。所有已跟踪文件在上次提交后都未被更改过。此外还表明当前目录下没有任何处于未跟踪的文件,否则 Git会在这里列出来。最后该命令还显示了当前所在的分支是 master,这是默认的分支名称。
现在在当前目录下创建一个新文件 README,保存退出后运行
git status
会看到该文件出现在未跟踪文件列表中:
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
可以看到新建的README文件出现在“Untracked files”下面。用
git add README
命令将该文件加入跟踪后再运行
git status
会看到 README 文件已被跟踪,并处于暂存状态
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
加入我们对仓库中一个叫newText.text的文件进行一些修改以后,再运行
git status
会看到
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
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: newText.text
文件 newText.text 出现在 “Changes not staged for commit” 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行 git add 命令(这是个多功能命令,根据目标文件的状态不同,此命令的效果也不同:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等)。现在让我们运行 git add 把newText.text 放到暂存区,然后再看看 git status 的输出
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: newText.text
可以看出两个文件都已经被放入了暂存区,下次提交的时候会一起被写入数据库。
提交修改
在我们新增、修改文件后,使用
git commit -m [initial project version]
命令提交修改,initial project version是提交时的说明,在这里写明做了什么修改,方便自己和别人过后查看代码。
提交代码之前必须先用add命令将修改后的文件加入暂存区,如果想跳过这一步,也可以使用
git commit -am [initial project version]
或者
git commit -a -m [initial project version]
来直接提交改动,这样会直接将已经加入跟踪的并且发生了改动的文件先加入暂存区然后提交,省略了add步骤。
设置忽略目录
有时会有些文件我们不希望纳入Git的管理,也不希望它们总出现在未跟踪文件列表,比如日志文件、配置文件等。我们可以在仓库的根目录下使用
touch .gitignore
创建一个名为 .gitignore的文件,然后使用
echo ["rule"]>>.gitignore
向.gitignore文件中写入要忽略的文件的规则,其中rule是字符串例如:
echo *.[oa]>>.gitignore
表示忽略所有以 .o 或 .a 结尾的文件
echo *~>>.gitignore
表示忽略所有以波浪符(~)结尾的文件
新增了.gitignore文件后也需要进行提交。
有些时候,你想添加一个文件到Git,但发现添加不了,原因可能是这个文件被.gitignore忽略了,如果你确实想添加该文件,可以用
git add -f [App.class]
强制添加App.class这个文件到Git,如果你想知道是哪一个规则限制了添加,可以使用
git check-ignore -v App.class
来检查,git会输出信息表明是第几行规则忽略了该文件。
查看已暂存和未暂存的更新
git status的显示的信息比较简单,仅仅是列出了修改过的文件,如果要查看具体修改了什么地方,可以用
git diff
来查看当前工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容,注意git diff只是显示还没有暂存起来的改动,而不是这次工作和上次提交之间的差异。所以暂存了所有更新过的文件后,运行 git diff 是什么都没有的。
查看暂存区文件与上次提交后的变化,可以使用
git diff --cached
查看两次commit之间的变化,可以使用
git diff [commit_id1] [commit_id2]
比较工作区与最新本地版本库,可以使用
git diff HEAD
比较工作区与指定commit-id的差异,可以使用
git diff [commit_id1]
撤销操作
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用--amend选项重新提交,如果只是想提交修改信息,则只需要输入
git commit --amend
输入指令后将会弹出文本编辑器,可以看到上一次提交的说明,按shift + i组合键进入编辑模式可以修改提交信息,修改完成后按esc退出编辑模式,再按shift + :组合键,输入wq,回车保存改动,提交说明就会被修改。
如果不是只修改说明而是想要补上其他的改动,则可以将其他改动先暂存,然后在提交的时候带上--amend:
git commit -m 'initial commit'
git add forgotten_file
git commit --amend
这样会将第二次提交和第一次提交合并,最终只产生一个提交。
如果我们将某个文件误加入了暂存区,可以使用
git reset HEAD <fileName>
将名为fileName的文件移出暂存区,这个文件将会回到已修改未暂存的状态。
如果我们想放弃改动将某个文件返回修改前的状态,可以使用
git checkout -- <fileName>
将名为fileName的文件恢复成未修改的状态,这个操作将会清空所有已经修改,需要谨慎操作。
在Git中所有已经提交过的改动都可以被恢复,即便在已经删除的分支中的提交,或者用 --amend 重新改写的提交,没有提交过的的改动无法恢复,对Git 来说它们就像从未存在过一样。
标签操作
Git可以对某一时间点上的版本打上标签,方便未来某个时间能回退到打标签时刻的状态
要列出所有tag:
git tag
显示的标签按字母顺序排列,标签太多的时候我们可以用特定的搜索模式列出符合条件的标签
git tag -l 'v1.4.2.*'
只显示v1.4.2系列版本
Git使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,在这里只举例轻量级标签的使用方法:
git tag v1.4
则打上了V1.4的标签
Git分支操作
创建切换新分支
git branch testing
可以新建一个名为testing的新分支,但是不会自动切换到该分支,要切换到该分支:
git checkout testing
也可以使用
git branch -b testing
一步完成创建和切换工作。
在切换分支的时候,如果暂存区或者工作目录里还没有提交的修改和你即将检出的分支会产生冲突,Git会阻止你切换分支,此时可以使用
git stash
将这些内容暂存起来,切换分支或者当再回到这个分支后,使用
git stash pop
方法就会将这些内容取出到当前分支。
删除分支
如果需要删除的分支不是当前正在打开的分支,使用branch -d直接删除
git branch -d <branchName>
如果我们在试图删除一个分支时自己还没转移到另外的分支上,Git就会给出一个警告,并拒绝该删除操作。如果坚持要删除该分支的话,就需要在命令中使用-D选项
git branch -D <branchName>
当我们删除一个分支时,Git只是删除了指向相关提交的指针,但该提交对象依然会留在版本库中,如果我们知道这个分支的Hash值,那么我们可以使用
git branch <branchName> <hashValue>
来恢复这个分支,如果不知道这个分支的Hash值,可以使用
git reflog
来查看所有所有的commit信息,包括所有branch的commit,甚至包括已经撤销的commit
分支的合并
git checkout master
git merge fixIssue
可以将fixIssue分支合并回master分支
关于分支的合并,需要注意的是,如果从master分支分离出来的分支fixIssue过后又合并进了master分支,如果此时master分支上没有其他新增的内容的话,合并成功后会出现Fast-forward的提示,表明实际上是将master的指针快进到了fixIssue的指针处,如果此时master分支上已经有了其他的改动,则Git会自动生成一个新的合并提交并将master的指针指向这个提交
处理冲突
如果在不同的分支中都修改了同一个文件的同一部分则在合并时会产生冲突,提示如下:
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
此时Git已经做了合并,但是没有提交,此时可以用git status来查看哪些文件产生了冲突:
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
包含没有解决的冲突的文件都会被标记为unmerged状态,从上面的结果可以看出index.html这个文件产生了冲突,用文本编辑器打开这个文件,会出现以下的内容:
<<<<<<< HEAD
var a = 1
=======
var a = 2
>>>>>>> fixIssue
Git使用=======符合将有冲突的部分分隔开
<<<<<<< HEAD
var a = 1
=======
表示当前分支的内容
=======
var a = 2
>>>>>>> fixIssue
表示合并进来的分支的内容,选择要保留的部分,将<<<<<<< HEAD、=======和>>>>>>> fixIssue标记删除,再用git add命令将修改好的文件标记为冲突已解决并加入暂存区,然后使用git commit提交。
在我们的日常开发中,使用pull request流程来提交代码,如果提交pr的时候提示有冲突,例如想将fixIssue分支合回master,最好在本地将master分支合并进fixIssue,在fixIssue中解决好冲突后,此时pr应该就会显示冲突已经解决可以自动合并,不要在master分支上合并冲突防止出现误操作破坏master分支。
管理分支
git branch命令除了能创建删除分支,也可以查看分支的信息,直接使用
git branch
会列出所有的分支清单:
iss53
* master
testing
带*号的是当前选中的分支,也可以使用
git branch -v
来查看每个分支最后一次提交的信息
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
还可以使用
git branch --merged
来查看哪些分支已经合并回了当前分支
iss53
* master
结果显示iss53这个分支已经合并回了master分支,如果不是有特殊用途要保留一般可以删除,因为这个分支的改动已经包含在master分支中,使用
git branch --no-merged
可以查看哪些分支还没有合并进当前分支
远程仓库的使用
远程分支的概念
远程分支是本地分支在Git服务器上的对应,一般我们都是使用GitHub的Git服务器服务,假如我们在GitHub上有一个仓库,当我们使用第一节提到的git clone将这个仓库克隆到本地的话,将会在本地复制一个与远程仓库一样的本地仓库,并给远程仓库起一个默认名字origin,假如远程仓库的默认分支叫master的话,将会在本地创建一个命名为origin/master分支,但你无法在本地更改其数据,接着Git 建立一个属于你自己的本地master分支,和origin上的master分支指向相同的位置
你不能直接修改origin/master分支的数据,但是你可以通过修改本地master分支的数据,然后推送到远程仓库的方式来改变origin/master分支的数据,或者通过拉取操作来把本地master分支的数据更新到和origin/master一样
假如你在本地master分支做了些改动,与此同时其他人向origin/master推送了他们的更新,那么服务器上的master分支就会向前推进,而与此同时,你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯,你的origin/master 指针仍然保持原位不会移动
推送数据到远程分支
使用
git push origin serverfix
可以将本地的serverfix分支推送到远程仓库的serverfix分支上,这里是简便写法,Git会自动将serverfix扩展为refs/heads/serverfix:refs/heads/serverfix,意思是“取出我在本地的serverfix分支,推送到远程仓库的serverfix分支中去,也可以使用
git push origin serverfix:serverfix
来达到相同效果,如果想让远程分支和本地分支使用不同的名字,可以用
git push origin localBranchName:originBranchName
来将本地叫localBranchName的分支推送到远程仓库,在远程仓库上的分支名字叫originBranchName
当你运行
git fetch origin
时,会同步远程服务器上的数据到本地。该命令首先找到 origin 是哪个服务器,然后从上面获取你尚未拥有的数据,更新你本地的数据库,然后把 origin/serverfix 的指针移到它最新的位置上
需要注意的是,在 fetch操作下载好新的远程分支之后,本地的serverfix分支并没有更新,本地只是多了一个不能操作的origin/serverfix 分支,必须要在本地的serverfix分支使用
git merge origin/serverfix
命令将远程分支的变化合并回本地分支
跟踪远程分支
假如远程分支不是自己创建的,则需要跟踪远程分支,假如其他人推送了一个新分支newBranch到远程仓库,则在调用git fetch origin时本地会出现一个origin/newBranch分支,使用
git checkout -b newBranch origin/newBranch
则本地会多一个newBranch分支,此时的分支叫跟踪分支,在跟踪分支里输入
git push
Git会自行推断应该向哪个服务器的哪个分支推送数据,在这些分支里运行
git pull
会获取所有远程索引,并把它们的数据都合并到本地分支中来,git pull相当于git fetch和git merge的组合,在克隆仓库时,Git通常会自动创建一个名为master的分支来跟踪 origin/master。这正是git push和git pull一开始就能正常工作的原因,跟踪分支可以和远程分支不同名:
git checkout -b anotherBranch origin/newBranch
删除远程分支
删除远程分支的命令是:
git push origin :serverfix
和推送远程分支的区别就是本地分支的名字为空
Git进阶用法
变基操作
如果有一个分支master,上面有两个提交A、B,此时开了一个新分支new,然后在new分支上提交了一系列提交,此时master也有了新提交,此时分支的情况如下图所示:
C new
/
A---B---D master
此时如果我们将new分支合回master分支,此时会在master分支上进行一次三方合并,生成一个新的提交D',现在分支的情况如下:
C new
/ \
A---B---D--- D' master
除了merge外,还有另一种整合两个分支的方法,就是rebase方法,回到合并new到master之前,假如在new分支上执行:
git rebase master
则分支的情况如下:
C---D’ new
/
A---B---D master
对master分支进行一次快进合并,现在的情况如下:
C---D’ new
/ master
A---B---D
此时相当于new分支是在D提交之后才开出来的分支,然后再提交了C提交,然后再做了一个补丁操作,再把master的指针移动到new分支指针的位置。使用rebase指令后,master和直接merge在内容上没有区别。
使用变基操作一般是想要得到一个能在远程分支上干净应用的补丁,比如某些项目你不是维护者,但想帮点忙的话,最好用变基:先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候,根据最新的 origin/master进行一次变基操作然后再提交,这样维护者就不需要做任何整合工作,只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。
变基操作除了可以在分叉的两个分支上进行,也可以将分支上的分支rebase到主分支上,假如目前的分支情况如下:
E---F anotherBranch
/
C---D oneBranch
/
A---B master
使用
git rebase --onto master oneBranch anotherBranch
此时的分支状况会变成
C---D oneBranch
/
A---B--C'--D'--E'--F' anotherBranch
|
master
变基操作虽然有用,但是最好是在还未同步到远程分支的本地分支上使用,不要在远程分支上使用。
变基操作的其他用途
当我们有了数个提交之后,突然发现其中的某一个提交是错的不想要了,或者想改变一下提交的顺序的时候,可以使用
git rebase -i [commitID]
-i 的意思是interactive,交互式操作的意思,例如当我们输入
git rebase -i HEAD~3
后,会出现如下的提示:
pick c24b68f3 删除节点崩溃
pick c1049e31 修改issue1087
pick b57a1f89 修改首页点击router图标数据错误issue
# Rebase 7a173fb4..b57a1f89 onto 7a173fb4 (3 commands)
#
# 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
# d, drop = remove commit
#
# 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
~
"~/Documents/mercku_ios/.git/rebase-merge/git-rebase-todo" 22L, 767C
这表明从当前指针往前数的三个提交被重新拿了出来,有机会能再次提交,注释中给出了提示pick代表使用本次提交,reword表示使用这次提交但是改变提交信息,sqush表示将这次提交与上次提交合并,并能改变提交信息,drop表示丢弃这次提交。
按command加i组合键进入编辑模式,可以修改关键字,修改完成以后按esc退出编辑模式,再按command加:,输入wq保存退出即可生效。
变基操作的其他用途
在利用github实现多人合作程序开发的过程中,我们有时会出现错误提交的情况,此时我们希望能撤销提交操作,让程序回到提交前的样子,总结了两种解决方法:回退(reset)、反做(revert)。
- 查看版本号:
git log
- 本地版本回退:
git reset --hard [commit id]
- 强制更新远程
git push -f