本篇文章介绍了Git原理、基本命令、如何在常见开发平台中使用Git以及如何使用Github等。

Git原理

介绍

Git是一个分布式版本控制系统,功能就像其名字一样,可以从两个方面来理解:一是版本控制,二是分布式。

  • 所谓版本控制系统,其作用是可以记录文件的每一次修改(改了哪里,改了什么,什么时候改的),当人们知道这些信息后,就可以方便地进行版本控制(如查看自己之前改了什么,恢复以前的某个操作等);
  • 而所谓分布式,则是指Git将版本信息分布地进行保存(并没有集中存储在某台电脑上),每个参与项目的电脑上都会保有一个版本信息库,这样的好处是信息不容易丢失,且配合Git强大的分支管理功能,能极大地提升协同工作效率。

Git存储格式

Git系统反映在文件上就是.git文件夹,储存在在项目的工程目录下,这个文件夹中存储了所有Git所需的信息(版本信息,分支信息,配置信息等),Git仓库本质上指的就是这个文件夹,这也是在克隆github库时后缀必须是.git的原因。
注:但是在github repository中并不能找到对应的.git文件夹,个人猜测应该是github将这个文件夹隐藏了(实际在电脑文件系统中.git也是隐藏文件夹),github会自动对其进行维护,用户并不需要管这个文件夹。

Git文件状态

  • 目录下的文件可分为tracked(已跟踪)和untracked(未跟踪)两种状态。
  • tracked:指处于Git控制下的文件,可以进行stage, commit等操作;
  • untracked:指被Git系统忽略的文件,可通过配置.gitignore文件来设置忽略哪些文件。
  • 而根据文件所在区域的不同,tracked文件又可分为三种状态:stagedcommittedmodified。为便于理解,首先引入Git系统的三个区域。

Git系统的三个区域

包括Working Directory(工作目录), Staging Area(暂存区), Repository(仓库)。

  • Working Directory:即为当前的工作目录;
  • Staging Area:是一个虚拟区域(并没有实际的文件夹与之对应),其存储了所有暂存的文件;
  • Repository:仓库区,也是一个虚拟的区域,所有已保存的文件都会存入这里。

三个区域的关系如下:
Git系统的三个区域

tracked文件的三种状态

staged:指已存入暂存区的文件,下次提交时会将暂存区中的文件提交到Git仓库。
committed:指已经提交到Git仓库中的文件,也可理解为未修改的文件。
modified:表示已修改,但还未提交到仓库中的文件。
一个文件的状态周期如下:
文件状态周期

  • 暂存区存在的意义:主要是为了保证commit的干净。可以选择性要提交哪些文件,而不是一次性把文件全部提交了,这样能保证把一些意义相似的修改都放在暂存区在一个commit下一次提交,而不至于将一些不相关的、无意间的修改加进去,保证了commit的纯洁性,降低commit粒度(一个大commit可以拆成多个小commit)。
    因此暂存区并不是为了暂时存放某些文件,而是为了更好地整理当前要commit的文件,因此规范上来说不应该在暂存区里存文件(要commit的文件选好后直接提交了就行了)

如何在各个系统上使用Git

Linux/Mac下:安装Git后,Git会被添加到环境变量中,可直接通过terminal来执行git指令;
Windows下:安装Git后,可通过Git Bash(命令行)和Git GUI(图形界面)两种方式使用。
注:目前IDE大多集成了Git的功能,直接在IDE里点一点就可以很方便地使用Git。但为了充分了解Git的工作方式和原理,还是需要学习一下Git的相关指令。

Git Bash

Git Bash是Git在Windows下模拟的Linux命令行环境,在这个环境下可以使用Git命令、常用的Linux命令等。
注:在Git Bash中复制粘贴可以直接右键,也可以用快捷键(复制:ctrl+ins,粘贴:shift+ins),但不能ctrl+c/v。

Shell

Bash是Shell的一种,首先介绍一下Shell。
Shell字面理解是“壳程序”(操作系统可以分为核心kernel和外壳Shell两部分),用于用户和系统内核间的交互。相当于是一个命令解析器,其接收用户命令,然后调用相应的内核指令,完成相应的功能。
Shell有很多种,如Bourne SHell(sh)、Bourne Again SHell(bash)、C SHell(csh)等,这些Shell最大的区别就是命令集的不同。
注1:在linux/mac中预设的Shell就是bash;在Windows中的Shell是cmd和PowerShell,cmd功能较为简单,PowerShell功能则非常强大,可以看作cmd的超集。
注2:如何区分各种shell?可通过命令的开头字符,如果是$, #则为bash($为普通用户,#为管理员root超级用户);如果是PS <当前地址>则为Powershell,如果直接是<当前地址>则为cmd。
注3:同时Shell又是一种编程语言,可自动执行一连串的命令,并具有定义变量、循环/分支控制结构等特性。

Git基础操作

从新建版本库->提交第一个文件

以下从Git常用的使用流程出发来介绍相关命令

  1. 创建版本库:git init,将当前目录变为git可管理的仓库
  2. 添加追踪文件:git add <文件名>,用来跟踪新文件(tracked);add .代表所有文件。
  3. 修改文件
  4. 暂存文件(stage):git add <文件名>,这是一个多功能指令,也可用于将文件添加到暂存区。add指令可理解为“添加内容到下一次提交中”。
  5. 提交文件(commit):
    • git commit -m "<提交说明>":将暂存区中的文件提交到仓库。
    • git commit -a -m "<提交说明>":直接将文件提交到仓库(跳过暂存区)。
  6. 推送文件到远程库(push):git push <远程库名字> <要push的分支名>。远程库名字一般为origin;如果推送的是主分支,分支名就为master
    注:初次push可以使用git push -u <远程库名字> <要push的分支名>来设置默认关联的远程库和远程分支,之后再提交就可以直接git push,无需再指定参数。

如何使用远程库

一般我们都是用Github作为远程库来使用的,因此下文远程库默认指的都是Github。

远程库与本地库关联

如果想使用远程库,需要本地库与远程库关联,具体方法有二:

  1. 如果本地库是从远程库clone来的,那么直接就是关联的,无需任何操作;
  2. 如果本地库是自己建的,则需要手动关联远程库,命令为:
    git remote add <name> <url>
    name: 自己给远程库起个名字,直接origin即可;
    url: 远程库url。

远程库名字 & url

  • 远程库名字约定俗成为:origin
  • 远程库url有两种写法:
  • git remote -v:查看远程仓库信息,-v参数为显示url地址。

克隆远程库

git clone <url>:克隆远程库到当前目录下(会直接克隆整个文件夹,所以没必要新建一个文件夹)。

Git初始化设置

必要的两个初始化配置:姓名和邮箱
git config --global user.name "<name>":设置姓名
git config --global user.email "<email>":设置邮箱
git config --list:查看设置
注1:加--global选项后,姓名和邮箱会保存到~\.gitconfig账户下,对当前账户下的所有git仓库都有效;不加--global选项,config则只会保存到当前目录的.git目录下的.gitconfig文件中,只对当前git仓库有效。
注2:Github会根据在Git中设置的邮箱来判断向它提交的用户,因此git的邮箱需要与github账号的邮箱保持一致。
注3:也可通过直接修改.gitconfig文件的形式来指定用户名和邮箱。

1
2
3
[user]
name = RenLi
email = renli1024@outlook.com

查看提交历史

git log:查看commit历史记录(出现END时按q退出),只有当前版本之前的信息。
git log --pretty=oneline:简略显示,在每一行显示一个提交,只包含提交说明。
git reflog:简略显示所有的提交信息。

如何撤销已经push到远端上的commit

如果发现push到github上的commit有错误,或者是有一点小修改没加不想再新开个push,可以选择撤销已push的commit,具体步骤如下:

  1. 先在本地回退到相应的版本:git reset --hard <版本号>
    注1:通过git log查看历史版本号;而且版本号只写开头几位就行。
    注2:--hard参数意思是抛弃当前工作区的修改;如果希望保留工作区的更改,仅仅版本信息回退到之前的,可使用--soft参数(其实就相当于把之前的commit & push撤销了)。
  2. 本地进行修改,重新commit。
  3. 重新向远端push:git push origin <分支名> --force。使用--force参数来强制覆盖掉远端的版本信息;普通的git push会提示本地版本落后远程版本,是交不上去的。
  • 注:如果回退之后又后悔了,则可以用git reflog查找之前的commid id(直接git log是查不到的),然后reset到之前的版本。

多人协作中的Git

开始工作前先同步,推送前先同步,保证你向远程库推送的修改不会与别人冲突。

分支操作

查看分支:git branch
创建分支:git branch xxx(分支名)
切换分支:git checkout xxx(分支名)
创建+切换分支:git checkout -b xxx(分支名)
合并某分支到当前分支:git merge xxx(要合并的分支名)
删除分支:git branch -d xxx(分支名)
强制删除未合并的分支 :git branch -D xxx(分支名)
若合并分支时存在冲突,需要手动解决冲突再提交。
git log –graph :查看分支合并图

其他命令

git status:查看工作区中的文件状态(是否有文件被修改过)
git diff:查看文件具体的修改内容(默认是比较版本库中和工作目录中的文件)
git rm --cached <文件名>:将某文件从tracked设为untracked,但硬盘上还会保留,只是不再Git不再跟踪了。
git rm <文件名>:删除版本库中的文件。
git checkout -- <文件名>:从版本库中恢复硬盘中被删除的文件。

设置.gitignore文件

除了通过命令使得不再跟踪某个文件,还可以设置.gitignore文件来指定哪些文件不再跟踪,遵循glob模式(shell所使用的简化的正则表达式格式)。
规则:
/:匹配模式前加/表示是相对根目录下的位置;匹配模式最后加/表示要匹配的是目录(及目录下的文件),不匹配文件;
*匹配任意个字符;
[]匹配括号内任意单个字符;
[0-9]使用短划线匹配一段范围内的字符,这个例子中是匹配0到9之间的任意数字;
?匹配任意单个字符。
#:注释
eg:
*.a:匹配所有.a类型的文件
foo:匹配所有名为foo的目录和文件。
foo/:只匹配名为foo的目录(不管是根目录下的/foo/目录,还是某个子目录下/child/foo/目录);
/foo/:只匹配根目录下的foo目录,而不匹配其他子目录下的foo目录;
常用.gitignore模板

VS集成Git工具的使用

更改:相当于提交commit,将修改提交到本地库
同步:先将远程库拉取,再将本地库推送到远程库,即先Pull再Push
推送:将所做的更改,存入远程版本库
提取(Fetch):从远程版本库获得最新版本
拉取(Pull):将远程版本库合并到本地版本库,相当于(Fetch+Merge)
变基到(Switch):切换分支(双击即可切换)
VS中Git小图标
VS右下角会有两个Git小图标,如上图所示。
铅笔:commit;右边的数字:发生修改但还未commit的文件数。
上箭头:点击后会跳到同步界面,这时可点击“推送”将更改推送到远程库;右边数字:commit了但还未Push的文件数。
工作流程:开始工作前先同步,把别人的提交更新到你的本地库;提交前再同步一下,再将别人的提交更新到本地库(这两次同步也可用“拉取”Pull代替,主要保证和别人的commit不冲突,如果是自己的项目就不用这么做了。且一定要commit前做,而不是push前做!否则还是会冲突);之后再提交、推送,更新远程库。

Git冲突(conflict)操作

为何会出现冲突?

  • 出现冲突的原因:本地和远程的两条版本线不一致。
  • Git本质就是维护一条版本时间线(即master),理想情况下这条时间线在各个机器上都应该是一致的,这证明了大家的工作进度都是一致的。如果出现了冲突,即表明了大家机器上的commit线不完全一致,可能的情况:
    1. 本地修改了相同的文件,但没提交,这时候pull是pull不下来的,系统会提示“会覆盖本地文件”,因此合并被拒绝。这种情况其实不能算严格意义上的冲突,因为本地并没有commit,也就不存在版本线冲突,只是因为pull会覆盖当前的修改,因此系统无法pull。
      解决方法:1、暂时放弃本地的更新,pull后在新的commit上再更新;2、本地交一次commit,这样就可以pull下来了(会有conflict),之后merge conflict即可。
    2. 本地修改了相同的文件,且commit了,这样pull后就出现了冲突,出现冲突后git首先会自动先更新那些只有一方更改的部分,之后对于两方都修改的部分,需要手动合并,之后再commit。
  • git会自动标出哪些部分是冲突的,如
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Git is a distributed version control system.
    Git is free software distributed under the GPL.
    Git has a mutable index called stage.
    Git tracks changes of files.
    <<<<<<< HEAD
    Creating a new branch is quick & simple.
    =======
    Creating a new branch is quick AND simple.
    >>>>>>> branch1

HEAD和branch1分别是两个分支,冲突的部分已经被写出来,根据实际情况修改后,删掉HEADbranch1<< == >>等标号即可。
注:修改后一定要再测试遍程序确认可以运行,因为git只会列出两者共同修改的部分,而对于那些只有一方修改的地方则会自动更新,因此就有可能出现前后不一致的情况(前边保存了HEAD版本的,后边只有branch1修改的部分被自动保存下来了),所以要再确认下git自动修改了哪些部分(IDE里一般都有这个功能)。

Git merge远程库后会自动覆盖掉我写的代码吗?

先说结论:不会。
git只会自动改一方没有改的文件,因此不存在你改了然后把你的修改覆盖了这种情况。大致列举几种git自动修改的情况:
1、远程改了你没改->改你的,将远程修改添加到staged里。
2、你改了远程没改->将你的修改添加到staged里(commit、pull后相当于改了远程的)。
3、你改了、远程也改了,出现冲突,要手动merge conflict,合并完后再commit。

  • git文件丢失的情况:git只会修改没改的,因此唯一的丢失情况就是别人不小心把你的文件改了,且你自己没动这部分文件,因此git在更新时会自动将这部分文件与远程别人修改的版本同步,也就是丢失了。
  • 但这种情况是可以在之前的版本信息中找回的(因为如果没有交到版本库里就根本就无法拉取远程库,只有交到版本库了才能拉取),因此可以认为git是没有丢失信息的风险的。

Github使用

设置SSH加密

watch:可理解为“观察”项目,对于一个项目,默认自己处于Not watching的状态,当你选择Watching,表示你以后会关注这个项目的所有动态,这个项目以后只要发生变动,如被别人提交了pull request、被别人发起了issue等等情况,你都会在自己的个人通知中心,收到一条通知消息,如果你设置了个人邮箱,那么你的邮箱也可能收到相应的邮件。因此watch只用来关注一些你特别在意的项目,若watch得太多通知可能会爆炸…

Star:可理解为“关注”或“点赞”,表示喜欢这个项目。同时github会记录你所有Star的项目,因此Star还可以作为“收藏”功能来用。

fork:可理解为把项目“叉”过来,当选择fork,项目会拷贝到你的repositories中,你自己就有了一份原项目的拷贝,当然这个拷贝只是针对当时的项目文件,如果后续原项目文件发生改变,你必须通过其他的方式去同步。
使用fork的场景:协同开发项目、修改开源项目时使用,这样你就可以在原项目的基础上(原项目的版本信息都保留了下来),对项目进行修改,并可通过pull request来将修改提交给原作者,如果作者merge了,你就成为这个项目的主人之一了~

clone & download & fork

  • clone是将整个git仓库拷贝下来(即包含.git/文件夹),你在上面做的commit都会基于之前别人的commit
  • download则是只下载源代码文件(不包含.git/文件夹),这之后就相当于是一个你的新项目了,你可以随意“魔改”。
  • fork则是在github端进行代码拷贝,适用于协同开发的情景,先fork-修改文件-交pull request。
  • 因此对于只想使用别人github上代码的情况,只是download即可。

协同开发

团队协作流程:负责人创立一个组织(organization),其他人加入组织,分配权限,之后在这个组织下创建仓库(respository),所有人都向这个仓库提交代码。
关于权限设置:
可以在organization范围内设置权限,即设置成员的write/owner等的权限;
也可以针对respository设置权限,即将成员的ssh key公钥(id_rsa.pub),在res内保存公钥,成员即可对这个仓库提交代码。
关于公钥和私钥:公钥是给外界的,私钥是只自己所有。公钥私钥的主要作用是验证身份,因为只有特定人有权限向仓库提交代码。你在提交时用你的私钥加密,别人用你的公钥就可以验证确实是你(因为私钥只有你自己有)。


Post Date: 2018-09-13

版权声明: 本文为原创文章,转载请注明出处