- Git中的文件状态:已提交(committed)、已修改(modified)、已暂存(staged)、未修改(unmodified)、未跟踪(untracked),除了未跟踪之外的状态均已跟踪,未跟踪的文件既不存在于上次的快照当中,也不存在于暂存区中。
- git status :显示当前工作区的文件状态。简短的形式为git status -s,输出如下:
M README MM Rakefile A lib/git.rb M lib/simplegit.rb ?? LICENSE.txt
每个文件名前面有两列标记,左边一列的标记表示暂存区状态,而右边一列的标记表示工作区状态,[,M]表示工作区中已修改但未暂存,[M,M]表示某个修改已暂存但工作区中有新的未暂存的修改,[A,]表示该文件已第一次添加到暂存区,[M,]表示某个修改已暂存且工作区没有新的修改,[?,?]表示文件未跟踪。
- git config:用于配置Git。Git中的配置是分级的,包括system、global、local、worktree、command,常用的是local(当前仓库)和global(全局)。
- git config --list :列出当前Git的配置项。
- git config --global core.editor emacs :设置默认文本编辑器
- git config --global http.proxy socks5://127.0.0.1:7890 :设置HTTP代理服务器
- git config --global --unset http.proxy :删除HTTP代理服务器配置
- git init :在当前工作目录初始化仓库,这将创建一个名为.git的目录,重复执行该命令并不会导致任何文件被覆盖。初始化仓库后,工作区中现存的文件状态为未跟踪。
- git add :将工作区的文件提交到暂存区,暂存区的文件列表保存在.git目录中的index文件,因此该命令相当于将指定的文件添加到index中。可以在文件名中使用通配符,git add .表示以一个整体提交当前目录,注意Git记录的是一系列的快照而不是文件差异,因此该命令不仅会考虑新建和修改的文件,还会考虑被删除的文件。
- git add *和git add .不同,后者以一个整体提交当前目录,而前者提交当前目录中尚存在的所有文件,对于当前目录中被删除的文件,git add *是不会考虑进去的。
- git diff :显示工作区、暂存区、各提交之间的差异。
- git diff :显示工作区与暂存区之间的差异,即工作区中还未暂存的修改。
- git diff --staged/--cached :显示暂存区与当前分支最后一次提交的差异,即下次提交(不带-a参数的git commit)将包含哪些修改。实际上该命令的完整形式为git diff --staged/--cached HEAD,可以使用特定的<commit_id>或者分支名替换HEAD以显示暂存区与特定提交的差异。
- git diff HEAD :显示工作区与当前分支最后一次提交的差异,即下次提交(带-a参数的git commit)将包含哪些修改。可以使用特定的<commit_id>或者分支名替换HEAD以显示工作区与特定提交的差异。
- git diff HEAD^ HEAD :显示当前分支最后一次提交和当前分支倒数第二次提交的差异。
- git diff topic master :显示master分支和topic分支的最后一次提交的差异。
- git diff HEAD -- ./test :显示工作区与当前分支最后一次提交的差异,但仅限在test目录下。
- git commit :将暂存区中的文件提交到分支,-m参数指示本次提交的注释说明,如git commit -m "my comments"。
- git commit -a :自动将所有已跟踪文件暂存并提交,因此用户可以跳过git add步骤。
- git commit --amend :这是一个撤销操作,如果在某一次提交中遗漏了某些文件,则可以通过带--amend参数的命令再次提交,此次提交会合并并替换上一次提交,并不会创建一个新的提交。
- git rm :用于移除已跟踪文件,默认情况下会从磁盘中删除该文件,对于已暂存修改的文件,需要使用-f参数强制删除。
- git rm --cached:移除已跟踪文件,但不会从磁盘中删除该文件,文件将变为未跟踪状态。
- git mv :用于移动(重命名)文件,如git mv README.MD README,这相当于:
$ mv README.md README $ git rm README.md $ git add README
如果手动执行了上述三个命令,Git也会自动识别到这是一个移动(重命名)操作。
- git log :查看提交历史, 这将显示最近到最远的提交日志。--pretty参数可以指定格式,如--pretty=oneline 将每个提交放在一行显示,除此之外还有--pretty=short、full、fuller。
- git log --since=2.weeks :--since参数限制提交时间,此例表示仅输出两周内的提交。--since和--after参数意义相同,反之有--until和--before。
- git log -S function_name :-S参数搜索提交内容,此例表示仅输出添加或删除了包括function_name内容的提交。除此之外--author=text搜索作者,--committer=text搜索提交者,--grep=text搜索提交注释说明。
- git log -- dir :--后面接文件名或目录名,表示仅输出关于该文件或目录的提交。
- git reset :用于回退当前分支或者回退某个文件。在Git中主要维护三个树:HEAD、Index、Working Directory,HEAD总是指向当前分支的当前提交对象,Index即暂存区表示预期的下一次提交,Working Directory(下称WD)即工作区。
- git reset命令本质上就是在操纵这三个树,只是不同的参数下操纵的步骤不同:任何reset命令的第一步都是移动HEAD所指向的对象(而不是移动HEAD自身),这意味着回退当前分支的最后一次提交,但Index和WD不会改变。git reset --soft命令执行到此就结束了,如果目标是HEAD~/HEAD^,则相当于撤销了最后一次提交,但暂存区和工作区中的内容不变。
- reset命令的第二步是使用HEAD所指向的对象来更新Index,注意此时HEAD所指向的对象已经是另一个提交了,此时暂存区的内容将更新为HEAD所指向的提交的内容,工作区中的内容不变。git reset --mixed命令执行到此就结束了,这也是reset命令的默认行为。
- reset命令的第三步是使用HEAD所指向的对象来更新WD,即工作区中的内容将更新为HEAD所指向的提交的内容,即与暂存区保持一致,该操作比较危险,因为这会覆盖掉工作区以前的内容,并且难以恢复。git reset --hard命令执行于此。
- 你可以为reset命令指定一个文件名或者目录,此时会跳过第一步,直接进行第二步或者第三步,这可以用于重置特定的文件,如 git reset HEAD README.md 将README.md文件重置到最后一次提交,并在暂存区中更新,这不会丢弃工作区对该文件的修改,相当于撤销了对该文件的暂存。
- git reset :相当于git reset --mixed HEAD,撤销所有暂存,工作区内容不变。
- git reset --soft HEAD^ :撤销最后一次提交,暂存区和工作区内容不变。如果要完全撤销最后一次提交,可以使用--hard参数,这将难以恢复。
- 如果你已经进行了某些提交,但发现仍需要进行一些新的修改,你可以创建一个新的分支,并将master分支回退(注意新分支不会回退),在新的分支中进行新的修改再合并到master当中:
$ git branch topic/wip (1)创建新分支 $ git reset --hard HEAD~3 (2)回退master分支,撤销最后3次提交 $ git switch topic/wip (3)切换到新分支,继续修改
- git checkout :用于恢复某个文件。
- checkout命令和reset命令类似,运行git checkout [branch]和运行git reset --hard [branch]基本相同,但有两个区别:(1)checkout命令是安全的,它并不会直接覆盖工作区,而是尝试合并当前工作区的内容,并且不会丢弃已经修改过的文件。(2)checkout命令修改的是HEAD自身,而不是HEAD所指向的对象。
- 在指定文件名的情况下,checkout命令不会移动HEAD,此时和指定文件名名的git reset --hard相同,如git checkout -- README.md :将README.md文件重置到最后一次提交,这会丢弃工作区对该文件的修改。
- 一般情况下checkout命令用于两个目的:切换当前分支、将某个文件恢复到某次提交的状态。
- git remote :列出已配置的远程仓库服务器,常用的命令是git remote -v。使用git clone命令时会自动添加远程仓库服务器(缩写为origin),也可以通过git remote add <shortname> <url>命令手动添加。
- git remote rename 可用于重命名远程仓库服务器的缩写。
- git remote remove 可用于移除某个远程仓库服务器,此操作还会删除该远程仓库有关的所有远程跟踪分支以及配置信息。
- git clone :从特定的地址克隆仓库到新创建的目录当中,并为远程仓库的每个分支创建跟踪分支。使用clone命令后,不带参数的git fetch命令将拉取每个跟踪分支的数据,不带参数的git pull命令将拉取每个跟踪分支的数据并进行合并。
- git fetch :用于从远程仓库拉取所有数据,这仅仅将数据下载到本地仓库,而不会进行任何合并操作。由于git clone命令会自动设置缩写为origin的远程仓库服务器,因此git fetch origin会抓取上次clone后新推送的所有内容。
- git pull :用于从远程仓库拉取所有数据并合并,相当于git fetch + git merge或者git fetch + git rebase,对于快进合并两者没有区别,对于三方合并,需要通过--rebase参数和--no-rebase参数指定合并的方式。格式为:git pull <远程主机名> <远程分支名>:<本地分支名>
- git pull origin master :将origin/master拉取并与本地的master分支合并。
- git pull origin main:master :将origin/main拉取并与本地的master分支合并
- git push :用于推送本地分支到远程仓库,语法为git push <remote> <branch>,只有你拥有远程仓库的写入权限,且没有人在你之前推送过该分支时推送才会成功,如果在此之前他人已经推送过该分支,你必须合并其内容才能推送。格式为:git push <远程主机名> <本地分支名>:<远程分支名>
- git push origin master :将本地的master分支推送到origin/master
- git push origin master:main :将本地的master分支推送到origin/main
- git push origin --delete master :删除远程仓库中的master分支
- git tag :Git支持标签,标签的对象是提交。此命令将列举所有已存在的标签。Git中的标签分为两类:轻量标签和附注标签,附注标签可以包含更多的信息,包括标签者的姓名、电子邮件、日期、证书等。
- git tag v2.0 :为当前分支的最后一次提交打上轻量标签v2.0
- git tag -a v1.4 -m "my version 1.4" :为当前分支的最后一次提交打上附注标签,-a参数表示这是一个附注标签,而-m参数表示标签的说明信息。
- git tag v1.2 <commit_id> :为当前分支的<commit_id>提交打上轻量标签。
- Git中的分支:分支本质上是一个指向提交对象的可变指针,在Git中每次提交都会创建一个提交对象,提交对象会记录该次提交的相关信息以及指向上一次提交对象的指针,分支是一个可变指针,指向某一个提交对象。默认情况下git init命令会创建一个master分支。有了分支的概念后,HEAD的定义可以更加明确:HEAD是一个指向分支的可变指针,即HEAD所指向的对象是一个分支,因此改变HEAD本身和改变HEAD所指向的对象是不同的,前者改变的是HEAD所指向的分支,而后者改变的是HEAD所指向分支所指向的提交对象。
- git branch testing :创建一个名为testing的分支,该分支将和当前分支指向相同的提交对象,Git通过HEAD记录当前分支。此命令只会创建新分支,不会切换到新分支。
- git checkout testing :切换到testing分支,checkout命令用于切换分支,分支必须存在,本质上该命令修改了HEAD本身。可增加-b参数,表示创建新分支并切换过去。注意切换分支的时候,最好保持工作区和暂存区是干净的,以免分支之间发生冲突。默认情况下使用checkout命令切换分支的时候,只会修改HEAD,而Index和WD中的内容是不变的,为了强制同步,可以使用-f参数。git switch testing具有相同的作用。
- git log --oneline --decorate --graph --all :输出提交历史、各个分支的指向以及分支的交叉情况。
- git merge :用于合并分支,该命令将参数指定的分支合并到当前分支。当你合并两个分支的时候,如果一个分支可以顺着走下去到达另一个分支,Git只需简单地重放路径中的修改就可以完成合并,这称为快进合并。例如,master分支指向的版本是另一分支testing的旧版本,此时将当前分支切换到master,并执行git merge testing,Git将testing分支的修改合并到master,master分支会移动并指向和testing分支一样的提交对象。
- 对如下分支情况:
A---B---C topic / D---E---F---G master
将当前分支切换到master,如果尝试合并topic,Git会先寻找master和topic的共同祖先即版本E,并尝试对版本E、版本C和版本G进行三方合并,合并的方法是Git将在master(当前分支)的基础上,重放从版本E(共同祖先)开始,直到topic(目标分支)的所有修改(这包括A、B、C),如果没有冲突发生,合并完成后将创建一个新的提交,将master移动并指向该分支,经过三方合并后创建的新提交有两个父提交:
A---B---C topic / \ D---E---F---G---H master
如果发生冲突,Git将暂停合并过程,直到你解决冲突之后才会创建这个新的提交。可以通过git status命令查看由于冲突而无法提交的文件,通过git add命令解决这些冲突,并通过git commit命令完成最后的合并。
- 对如下分支情况:
- git branch :不加参数地运行该命令,可以列出当前的所有分支,加上-v参数可以显示每个分支的最后一次提交。--merged/--no-merged参数表示仅显示已合并/未合并到当前分支的分支。
- git branch -d testing :-d参数用于删除分支。
- Git的远程分支:对于远程分支,可以远程引用其中的的分支、标签等,远程引用的整个列表可以通过git ls-remote命令查看。git clone命令会设置名为origin的远程仓库并创建一个引用远程分支master的本地分支,完整的缩写名为origin/master。本地的origin/master分支的指向是不变的,除非从远程仓库服务器拉取数据。一旦他人向远程仓库推送了新的提交,本地的origin/master分支和远程仓库中的origin/master分支就不会同步。
- 从一个远程分支检出的本地分支称为跟踪分支,对于跟踪分支,Git会在git pull命令执行的时候自动识别出哪些分支拉取后需要合并。git clone命令创建的master分支会自动跟踪origin/master。手动创建跟踪分支的命令为git checkout -b <branch> <remote>/<branch>,等价的写法是git checkout --track <remote>/<branch>。
- git push origin --delete <branch> :从远程仓库删除一个远程分支。
- git rebase :在Git中整合多个分支的方法有两种:merge(合并)和rebase(变基)。rebase和merge在没有发生冲突的情况下所产生的最终提交是完全相同的,区别在于提交历史不同。对于如下分支情况:
A---B---C topic / D---E---F---G master
切换当前分支为topic,如果执行git rebase master或者git rebase master topic,Git将版本E直到版本C的所有修改(包括A、B、C)都在版本G的基础上执行,但不会创建一个具有两个父提交的提交对象,而是删除topic分支,并沿着master分支创建一个新的提交对象,就好像topic分支上的所有修改都是串行在版本G一样,完成合并后如下。注意在这个过程中,master分支(目标分支)并没有移动,仍然指向版本G,而topic分支(当前分支)将移动指向新的提交对象。
D---E---F---G master---A'---B'---C' topic
在存在多个分支的情况下,你可以选择合并某个分支,而让另一个分支继续保持,只需使用--onto参数即可:
o---o---o---o---o master \ o---o---o---o---o next \ o---o---o topic
执行git rebase --onto master next topic命令,参数中指定三个分支,分别表示:将重放修改的分支(相当于当前分支)、要跳过的分支、被合并的分支(目标分支)。执行后如下,注意master和next分支均没有移动,而topic分支移动指向新的提交对象。注意被重放的仅仅是topic分支从next分支分歧后的修改,next分支中的修改没有被重放。
o---o---o---o---o master | \ | o'--o'--o' topic \ o---o---o---o---o next
在团队开发中,使用变基是有风险的,因为变基会删除提交历史,如果有人在变基之前拉取了数据,那么本地仓库将保留这些提交,而在远程仓库中这些提交可能因为变基而被删除了,更复杂的是如果一些人使用变基而另一些人使用合并,每个人的本地提交历史将变得十分混乱。