使用git畅游代码的海洋
如果把互联网上的纷繁代码比作一片海洋,那么git就是在这片海洋上航行的船只,正所谓“水可载舟,亦可覆舟”,git使用恰当可以远征星辰,不然可能会坠入无穷无尽的代码海洋无法自拔。书回正传,我们的征途是星辰大海!
扬帆起航
git的下载安装暂且不表,可参考网站https://git-scm.com/downloads。
git的安装只是个入门条件,下面如何使用git命令控制代码才是远征的基础。那么从何开始呢?让我们一步步说起。
要想扬帆起航,首先得有艘帆船吧。我们要在本地建立一个项目,之后将本地项目初始化为git仓库,可以使用
git init [--bare]
初始化本地项目,执行后会在当前执行目录下生成.git文件夹,这也就是我们的帆船了。[--bare]可选参数,可初始化裸仓库,裸仓库可以与源码项目分离,此时在当前目录下不会生成.git文件夹,而是直接生成.git文件夹下的包括hooks、info、objects、refs共四个文件夹和config、description、HEAD三个文件。
在初始化裸仓库后若想关联源码,只需进入hooks目录新建post-receive.sample文件,并添加如下内容:
git --work-tree=<project-dir> --git-dir=<local-url> checkout -f
其中<project-dir>为本地项目文件路径,<local-url>为本地git仓库路径。
对于当前目录下的.git文件夹所代表的本地仓库,不同的帆船有不同的配置,我们使用
git config –list
显示当前git配置。如果内容过多可以使用上下键翻页,查看结束按q退出即可。同时使用
git config –e [--global]
以vim形式编辑修改git配置文件。
或
git config [--global] user.name “name”
形式修改指定配置,其中[--global]可选,为全局配置,name为用户名配置。
git仪表盘
图 1 Git指令关系图
建立git仓库作为帆船后,就可以在当前目录下任意使用git指令遨游了。我们需要先认识下git控制的命令仪表盘。
如图1所示,git整艘船主要分为四大区域,包括Remote远程仓库,Repository本地仓库,Index/Stage暂存区,Workspace工作区,不同区域之间可以使用相关指令进行代码操作。在上节使用git init创建的文件夹下,会生成名为.git的隐藏文件夹,四大区域的配置都储存在该文件夹下。其中在远程仓库和本地仓库中,储存有不同的branch分支,工作区和暂存区的文件只能针对某一分支进行修改提交操作,当然也可以使用分支操作指令对不同的分支进行增删合并等操作。
针对一些仓库的操作指令,使用前请务必保持头脑清醒,不然你的一个蝴蝶煽动翅膀似的操作,可能会引发仓库里的一场代码风暴,造成不可挽回的结果。
根据不同命令操作区域,可以将git命令大致分为全局显示信息、工作区与远程仓库交互、工作区与本地仓库交互、工作区与暂存区交互、暂存区与本地仓库交互、本地仓库与远程仓库交互、以及仓库内部分支操作。下面对这些命令进行了粗略的统计介绍,详细使用方式可参考git官方文档介绍,如有不足还请补充。
显示信息
git help [command]
获取命令的帮助信息。
git status
显示所有变更文件
git log [--stat] [--graph]
显示当前分支的版本信息。[--stat]参数指定显示commit发生变更的文件。[--graph]参数以数据图形式查看合并分支记录。
git blame <file>
显示文件的每一行最后修改的版本和作者
git show [commit][:filename]
显示某次提交的变更内容。其中[commit]为某次提交版本,也可在:后边加参数[filename]指定查看某个文件内容。
git diff [HEAD] [first-branch] [second-branch]
显示文件差异。无参时比较缓存区和上一次commit的差异;[HEAD]为工作区与当前分支最新commit的差异;或者两个分支之间的差异。
git reflog
显示已执行过的所有git动作日志。
工作区与远程仓库
git pull <remote> <branch>
拉取远程仓库的变化,并与本地分支合并。<remote>为远程仓库名,<branch>为远程仓库中的某一分支名。
工作区与本地仓库
git checkout [–b] <branch> [tag]
将暂存区切换到分支名。其中[-b]参数可选,当分支不存在时则创建,<branch >必须,为本地仓库分支,[tag]可选,指定切换到仓库分支中的某条标签,不标注则默认为切换分支的最近一次提交。
工作区与暂存区
git add <dir>
添加指定目录到暂存区,<dir>为添加路径,允许多个,包括子目录都会添加到暂存区中等待提交。
git rm [--cached] <file>
删除暂存区中的文件,<file>为暂存区中要删除的文件全路径,[--cache]可选参数,只会停止继续追踪指定文件,但该文件目前仍然保留在暂存区。
git mv <file-old> <file-new>
修改暂存区中的文件名,<file-old>为原文件全路径,<file-new>为修改后的文件全路径。
git tag
查看暂存区中的所有标签信息。
git tag –a <tag> [commit]
新建一个标签。[tag]为标签名;[commit]为指定的一次从暂存区到本地仓库的提交中,默认为最新一次提交。
git tag –d [tag]
删除本地标签。
暂存区与本地仓库
git commit [--amend] [–m <message>] [file] [-a] [-v]
从暂存区提交到本地仓库。其中[--amend]重做上次从本地项目到暂存区的commit,当代码与上次提交相比无变化时使用,只修改上次commit的<message>内容;[-m]参数为提交信息,<message>必写且详写,以区别提交代码的修改内容;[file]为指定暂存区中的文件;[-a]可直接提交项目中的变化到本地仓库,在没有新增文件时不需每次先git add提交到暂存区再提交到本地仓库;[-v]可以在提交时显示所有变化文件的diff信息。
git cherry-pick [commit]
选择一个commit版本合并到当前分支,[commit]为暂存区中的commit版本。
本地仓库与远程仓库
git remote [-v]
查看关联的远程仓库信息。[-v]可以查看详细信息。
git remote add <remote-name> <remote-url>
本地路径关联远程仓库。
<remote-name>必要参数,为远程仓库的名字,默认是origin。
<remote-url>必要参数,为远程仓库的地址,git服务器通常都是以.git结尾。
git remote remove <remote-name>
删除关联的远程仓库。其中<remote-name>为远程仓库的名字。
git push [remote] [branch] [--force]
推送本地指定分支到远程仓库。[remote]为远程仓库名,[branch]为本地分支名,[--force]为强制推送本地到远程,如有冲突则覆盖。
git fetch <remote>
将远程仓库拉到本地仓库。<remote>为远程仓库名。
git clone <url> [name]
创建一个本地仓库。<url>必须,可以是远程git服务器上的仓库,也可以是本地仓库。[name]选填是创建的新仓库名,默认与原仓库名一致。
分支指令
git branch [-r] [-a]
查看分支信息,无参只会查看本地仓库所有分支,[-r]是远程仓库所有分支,[-a]则是包括本地和远程仓库所有的所有分支。
git branch [branch-name] [commit]
在本地仓库新建分支,但暂存区仍然指向当前分支。其中[branch-name]为新建的本地仓库分支;[commit]可将分支指向指定commit版本。
git branch –track [local-branch-name] [remote-branch]
新建一个分支,并连接指定的远程分支。其中[local-branch-name]为本地仓库新建分支,[remote-branch]为远程仓库分支。
git branch –set-upstream [local-branch] [remote-branch]
连接本地仓库分支与远程仓库分支,其中[local-branch]为本地仓库已有分支,[remote-branch]为远程仓库分支。
git branch –d [branch]
删除本地仓库分支,[branch]为本地仓库中的已有分支。
git branch -m [branch-old] [branch-new]
修改本地仓库分支,其中[branch-old]为原分支,[branch-new]为改名后的分支。
git branch –dr [remote-branch]
删除远程仓库分支,[remote-branch]为远程仓库中的分支。不推荐使用,如果远程仓库未更新,可能会执行失败,推荐使用git push origin-delete [remote-branch]。
git merge <local-branch>
合并指定分支到当前分支,<local-branch>为本地仓库中的已有分支。
git rebase <remote-branch>
将当前分支的提交复制到指定的远程分支上,<remote-branch>为指定远程仓库中的已有分支。
git reset [--mixed|--soft|--hard] [commit]
重置仓库索引,重置一旦清空后的内容不会在仓库历史版本中留下历史记录。[--mixed]为默认参数,重置后只在工作区保留原节点修改文件,清空暂存区和本地仓库并均恢复到指定重置节点;[--soft]为软重置,重置后在工作区和暂存区均保留原节点修改文件,清空本地仓库并恢复到指定重置节点;[--hard]为硬重置,重置后均不会保留原节点修改文件。[commit]为要重置的节点号。
git revert [commit]
还原文件到之前修改提交节点时,会在仓库历史版本中留下历史记录。[commit]为要还原的节点号。
常规操作
git这艘大船虽然功能繁杂,但是用起来是有章可循的。入门之后就驾驶下这艘大船来试试吧。
一般git有三种工作流程,包括Git flow,项目存在两个长期分支(主分支master和开发分支develop),适用于基于版本发布的普通项目;Github flow,只有一个长期分支(主分支master),适用于持续发布的小型项目;Gitlab flow,项目存在多个长期分支,其中主分支master是其他所有分支的上游,只有上游分支采纳的代码才能应用到其下游分支,适用于长期维护的大型项目。
图2展示了一次项目git流程演变过程,在master主分支上有Tag1-Tag4四次代码更新,其中基于Tag2对应的版本1号创建了新的branch1分支,新分支创建后自动生成了版本2号并打上了Tag2-1标签,之后master主分支和branch1分支都同时进行了代码演变,在branch1分支提交的版本4号及标签Tag2-3之后,branch1分支合并到了master主分支,合并前master主分支位于版本5号,合并后可能重新生成版本6号,并打上新的tag4标签,之后master主分支修改提交为版本7号,而branch1分支则停留在tag2-3标签的位置处。
图 2 Git流程示意图
在这份项目流程中,分支创建之后,可能在不同的阶段修改提交文件,根据对文档的读取权限范围,我们可以形象地将这些阶段划分为三种身份类型,船长、水手和游客。船长身份,作为项目管理者,主要负责远程仓库和本地仓库之间的分支操作,协调分支冲突;水手身份,作为项目贡献者,主要负责某一分支的迭代更新;游客身份,作为项目使用者,只是访问使用仓库及其分支内容,不能提交任何修改。同一人在项目的不同阶段可以是其中任意一种身份,下面以这三种身份为维度简单介绍下使用到的相关git指令步骤,并辅以示意图方式直观解释git指令执行前后git项目变化。
项目使用者-游客
作为git项目的游客,当然只能将项目从远程仓库拉取到本地使用,期间除了切换仓库分支外不会涉及其他远程操作。
git pull origin master
拉取远程仓库origin的master分支到本地。
git checkout branch1 tag1
切换到分支branch1。
项目贡献者-水手
作为项目的水手,除了可以使用游客的功能指令外,还会涉及到修改工作区文件,并将工作区文件提交到暂存区和仓库等任务。通常水手只需要维护仓库中的某一条分支并只对该分支负责,因此水手更注重工作区的代码文件修改工作。
git pull origin dev:branch1
拉取远程仓库origin的dev分支到本地,并与本地branch1分支合并。工作区中文件即显示branch1分支,可在工作区做文件修改操作。
git add .
在工作区的文件修改之后,可先添加当前目录所有文件到暂存区,之后可继续修改工作区其他文件,也可将暂存区文件提交到本地仓库。
git rm –cached file
针对工作区编译生成的配置file文件,一般不需提交到仓库,可使用该命令将file文件从暂存区删除并停止后续追踪。另外一种添加忽略文件的方式,在.git文件夹的同级目录下新建.gitignore文件,在该文件中根据规则增加要忽略的文件路径,之后将该文件提交到本地仓库中。
git commit –m “commit message 1”
在确保工作区的所有修改文件均已提交到暂存区后,便将暂存区的修改提交到本地仓库,同时附带当次提交信息。每次提交都会在本地仓库生成一个新的提交commit版本号,由于提交的commit版本号是冗长的sha1码,所以为了方便后期溯源,通常会在主要的commit号版本上再打一个鲜明的标签以作标记。
git tag tag1 1
在提交的commit版本号为1的节点上打标签,打上标签后的commit号便可使用简短的标签名tag1访问,以便后期对该节点溯源。
git push origin branch1:dev –tags
将本地branch1分支及相关标签推送到远程仓库origin的dev分支。如果本地branch1分支已经与远程dev分支建立追踪关系,也可直接使用
git push origin branch1 --tags
指令。
git branch –set-upstream-to=origin/dev branch1
设置本地仓库的branch1分支与远程仓库origin的dev分支的追踪关系。通常从远程分支pull到本地的分支都已经建立了追踪关系,不需要手动修改。
git branch –vv
查看本地分支及追踪的远程分支信息。
项目管理者-船长
作为项目的船长,自然拥有整个git这艘大船的项目所有权限,除了使用水手的操作指令外,另需完成分支增删合并等任务。通常船长是项目仓库的创建者,负责管理维护仓库的各分支关系,对项目的整个仓库负责,因此相较于水手,船长更注重仓库的分支管理相关工作。
git clone https://github.com/xxx.git -b dev
克隆远程仓库的dev分支到本地,默认会将文件更新到本地仓库建立的同名dev分支。
git checkout –b branch1 origin/dev
在本地仓库新建branch1分支,与远程仓库origin中的dev分支对应,并在本地切换到branch1分支。如果不指定远程仓库及分支信息origin/dev,则默认从本地仓库dev分支创建。至此可以切换为水手身份,从该分支更新代码,并将修改文件提交到该branch1分支。
git add .
git commit –m “modify file commit”
在完成对工作区文件的修改之后,使用水手身份将工作区的修改提交到本地branch1分支。
git checkout dev
切换到本地仓库的dev主分支,作为本地仓库与远程仓库代码合并的操作分支。
git fetch origin dev
拉取远程仓库origin中的dev分支到本地仓库当前dev分支
git pull
将本地仓库dev分支的文件修改合并到工作区。
git merge branch1
将本地仓库的branch1分支合并到当前dev分支。如果当前dev分支与branch1分支有冲突,需要根据冲突文件提示分别修改,之后再重新合并该分支。
git checkout branch1 build/files
或者只将branch1分支的build/files目录下所有文件合并到本地仓库的当前dev分支。同样需要做冲突处理。
git push origin dev:dev –tags
向远程仓库origin中的dev分支并推送本地仓库dev分支。推送时如果确认以本地分支覆盖远程分支,则可使用
git push —force origin dev:dev –tags
强制推送。最后如果想删除远程分支,有以下两条指令
git push origin –delete dev
git push origin :dev
这两种方式都可以删除指定的远程仓库origin中的dev分支。
应急预案
上面的图2Git流程图简单涉及了一次版本演变过程中的一些git信息,包括分支切换,打标签等,看上去简单易懂,而实际我们工作中驾船航行时却并不总是风平浪静。
通常出现的紧急情况需要修改分支版本,包括变基、还原、重置等操作,针对不同场景需要选择不同的操作方式。
变基
git rebase master
变基会将当前分支的修改文件复制到master分支,同时创建新的commit版本号并修改项目的历史记录。当master分支已经更新,且确认当前分支与master分支没有冲突,那可以使用变基以便当前分支获取master分支的更新。
重置
git reset 1
将HEAD重置到历史提交版本1的状态,还原仓库和暂存区的文件与提交版本1一致,工作区维持修改文件状态。
还原
git revert 1
重新创建一次提交版本节点,文件状态与历史提交版本1一致,工作区、暂存区与仓库均保持一致。提交版本2相对于提交版本1新增了file.txt文件,执行该指令后,在提交版本3中将恢复到提交版本1的状态,因此提交版本3相对于提交版本2则删除了file.txt文件。
强制远程覆盖本地
git fetch –all
拉取远程所有仓库到本地仓库,工作区不会有任何合并更新。
git reset –hard origin/dev
把工作区HEAD指向最新的远程仓库origin中的dev版本。
待补充
除此之外在驾驭git这艘大船时肯定还会出现各种意外,届时将酌情补充。