Git

1.SVN和Git

集中式(svn):svn存放的是差异,每次回滚的速度慢

  • 优点:代码放在单一的服务器,便于项目的管理。
  • 缺点:
    • 服务器宕机:代码得不到保障
    • 服务器炸了:整个项目的历史记录丢失

分布式(git):每次存放的都是项目的完整快照,需要磁盘空间相对大一点。

  • 客户端并不只是提取最新版本的文件快照,而是把代码仓库完整的镜像下来。
  • 存放的不是版本和版本直接的差异,他存放的是索引(所需磁盘空间最小)

2. 初次运行 Git 前的配置

Git 提供了一个叫做 git config 的工具(译注:实际是 git-config 命令,只不过可以通过 git 加一个名字来呼叫此命令。),专门用来配置或读取相应的工作环境变量。而正是由这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:

  • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
  • ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
  • 当前项目的 git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

用户信息

第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一起被永久纳入历史记录:

1
2
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

查看配置信息

要检查已有的配置信息,可以使用 git config --list

3. Git基本概念

区域

1
2
3
4
5
* 工作区 (沙箱环境 git不会管理 随便更改操作)

* 暂存区 (记录文件的操作)

* 版本库 (最终的代码实现提交到这里 .git目录就是版本库)

初始化git

1
2
3
4
* git init (初始化仓库 生成.git文件)
* git config --global user.name "Is zyd"
* git config --global user.email 1426593075@qq.com
* git config --list

.git目录下文件的介绍

  • hooks (钩子函数的一个库 类似于回调函数)
  • info (包含一个全局性的排除文件)
  • objects (目录存储所有数据内容)
  • refs (目录存储指向数据(分支)的提交对象的指针)
  • config (文件包含项目特有的配置选项)
  • description (显示对仓库的描述信息)
  • HEAD (文件目前被检出的分支)
  • logs (日志信息)
  • index (文件保存暂存区的信息)

Git对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
git核心本质上是一个键值对数据库。可以向该数据库插入任意类型的内容,
他会返回一个键值,通过该值可以在任意时刻再次检索该内容。
- echo "hello" | git hash-object --stdin
- 这句命令返回一个hash值用来标识这句话 但是并没有写到数据库中
- 内容不一样对应的hash值不一样
--stdin 从标准输入中读取内容

- echo "hello" | git hash-object -w --stdin
- 这句命令返回一个hash值用来标识这句话 并写到数据库中
- 查看有没有存在 可以通过 find ./ -type f 找对应hash的文件
- 里面的内容是压缩的 通过 git cat-file -p hash注意前面的那两个字母也加上
- git cat-file -t hash 查看git对象的类型 blob

- 将新创建的文件添加到git数据库中即生成一个git对象
- git hash-object -w ./a.txt

- 如果文件更改git数据库里面不会自动的添加要手动添加过去 这时会在添加一个git对象
- git hash-object -w ./a.txt

- 实质上Git对象是一个KYE:VALUE hash/value
- git对象不能当作项目的一次快照 只是组成项目的一部分

- 存在的问题?
- 记住文件的每一个(版本)对应的hash值并不现实
- 在git中,文件名并没有被保存,只能通过hash得到内容
解决:树对象

- 注意:此时的操作只是针对本地数据库进行操作,不涉及暂存区。


* 树对象
* 树对象能够解决文件名保存的问题,也允许我们将多个文件组织到一起。
通过update-index,write-tree,read-tree等命令来构建树并放入暂存区

* 构建树对象
- git update-index --add --cacheinfo 100644 915c628f360b2d8c3edbe1ac65cf575b69029b61 test.txt
- 文件模式为100644 表明这是一个普通文件
- 文件模式为100755 表明这是一个可执行文件
- 文件模式为120000 表明这是一个符号连接
- --add 因为此前该文件并没有在暂存区中 首次要加add
- --cacheinfo 因为要添加的文件在git数据库中,没有位于当前目录下

* 暂存区做一个快照生成一个对象放到git数据库中
- git write-tree
对象类型是一个树对象
树对象里面的内容是暂存区的快照(项目的快照)
* 暂存区中文件名字不变 如果改变文件的内容,就会重新生成一个hash

* 存在的问题?
- 不知道hash值对应的是哪一个版本
- 不知道这个版本的一些基础信息

* 提交对象
- 提交对象完美的解决了上面的问题
- 本质就是给树对象做一层包裹包含项目的基础信息
- commit-tree创建一个提交对象,为此需要指定一个树对象的hash值,以及该提交的父提交对象
- echo "second commit" | git commit-tree 019fb2c522b604cd94929085bbac93d60e2f2063 -p d248eb19a125c

- 真正代表一个项目的是一个提交对象(数据和基本信息)这是一个链式的!!

4. Git操作

取得项目的Git仓库

初始化git

1
git init (初始化仓库 生成.git文件)

添加到暂存区

1
2
* git add ./   首先将工作区(文件)做成git对象放到版本库 然后再放到暂存区 但是这里没有生成树对象
* git ls-files -s 查看暂存区的当前状态

添加到版本库

1
* git commit -m '提交的信息'

从现有仓库克隆

这就需要用到 git clone 命令。

1
2
3
$ git clone git://github.com/schacon/grit.git
这会在当前目录下创建一个名为grit的目录,其中包含一个 .git 的目录,用于保存下载下来的所有版本记录,然后从中取出最新版本的文件拷贝。如果进入这个新建的 grit 目录,你会看到项目中的所有文件已经在里边了,准备好后续的开发和使用。如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:
$ git clone git://github.com/schacon/grit.git mygrit

记录每次更新到仓库

作目录下面的所有文件都不外乎这两种状态:已跟踪或未跟踪

已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是未更新,已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存区域。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。

img

  • 高级命令(crud)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
* git init               初始化   
* git status 查看当前状态
* git diff 查看当先做的哪些更新没有暂存
* git diff -cached 查看哪些已经暂存了准备下次提交
* git commit -m "注释" 提交项目
* git commit -a -m git跳过暂存区直接提交,跳过add
* git add ./ 添加暂存区
* git commit -m "rename zx"
* git log --oneline 查看提交历史记录
-p 仅显示最近的2次更新

* rm yd.txt 删除文件 暂存区里没有文件 版本库多了一个提交对象不过没有内容
* 删除文件属于修改操作 跟上面的提交步骤一样
*进行了2步先将工作区的文件删除,再将暂存区的文件删除
* mv z.txt zx.txt 重新起名字跟已修改一样的操作

* git commit --amend 这个命令可撤消刚才的提交操作,如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交

结论

1
2
3
4
5
* 一次完整的项目提交 包括至少一个提交对象 一个树对象 0或多个git对象
* 工作目录中文件只有两种状态 已跟踪(只要第一次add就跟踪上了) 未跟踪
* 已经跟踪的文件还有三种状态 已提交 已修改 已暂存
* 如果一个已经提交的文件再次修改要重新添加到暂存区否则显示已修改状态
* 如果一个文件暂存完了没有提交前还要在修改 这时会出现一个暂存一个已修改的情况需要重新add

忽略某些文件

一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。来看一个实际的例子:

1
2
3
$ cat .gitignore
*.[oa]
*~

第一行告诉 Git 忽略所有以 .o.a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的,我们用不着跟踪它们的版本。第二行告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。此外,你可能还需要忽略 logtmp 或者 pid 目录,以及自动生成的文档等等。要养成一开始就设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。

文件 .gitignore 的格式规范如下:

  • 所有空行或者以注释符号 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式匹配。
  • 匹配模式最后跟反斜杠(/)说明要忽略的是目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。

我们再看一个 .gitignore 文件的例子:

1
2
3
4
5
6
7
8
9
10
11
# 此为注释 – 将被 Git 忽略
# 忽略所有 .a 结尾的文件
*.a
# 但 lib.a 除外
!lib.a
# 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
/TODO
# 忽略 build/ 目录下的所有文件
build/
# 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt

Git分支操作

分支就是为了保护代码方便更改存在的 假如master里面的提交对象完美了就可以在创建一个分支 添加功能如果可以就可以master合并 不行的话就可以删除这个分支 这样对于master没有影响

  • 新建一个分支到一个提交对象上面 这样做的好处是实现版本回推但是不改边主仓库的东西 用完 删除这个分支就可以了特别方便

分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
* 每一个功能都可以开一个分支,不影响主线的分支
* 分支就是一个活动的指针就在提交对象的前面指向最新提交
* master默认是主分支
* git branch 显示分支列表
* git branch test 会在当前的提交对象上创建一个分支

* git checkout test 将分支切换到test上面来

* git branch -d test 合并之后删除
* git branch -D test 删除分支 不能自己删自己

* git log --oneline --decorate --graph --all 查看完整的分支图(没删除前)

* git config --global alias.lol ‘log --oneline --decorate --graph --all’ 陪别名

* git branch -v 查看分支的最后一个提交

* git branch test hash 新建一个分支到hash所对应的提交对象上,就是创建一个分支到一个之前的hash版本上

* git checkout -b test 创建分支并且切换过去

* git merge hotbug(分支名)
  • git checkout name

    • 每次切换都要动三个地方HEAD、暂存区、工作目录
    • 切换分支的时候一定要提交完的时候再切否则会出现问题
      *每次切换分支前当前分支一定要是已提交状态 ,如果有第一次未暂存的 或 未提交的 会污染主分支 *
    • 如果第一次提交了再修改的时候没有提交他就不让切换分支了

合并分支一定要注意顺序 后面的可能会过期还会存在bug 会产生冲突

  • 快速合并 一条分支 快进合并

  • image-20200819101638197
  • 典型合并 多条分支 会有冲突(打开冲突文件看哪里要留 然后暂存提交)

    image-20200819101608222
  • 同事之间的冲突才是最麻烦的

存储

1
2
3
4
* 解决的问题 不想过多的创建提交比如iss53那个分支,不想为了1点点的工作而提交
* git stash list 查看存储
* git stash apply 拿出栈顶的元素 但是不会消除
* git stash drop 名字

后悔药

1
2
3
4
5
6
* 工作区撤回在工作目录中的修改
* git restore filename 本质是相当于重置
* 暂存区撤回自己的暂存
* git restore HEAD filename
* 提交区注释写错了修改注释,或者有几个文件没有加 先add在执行
* git commit --amend

reset

1
2
3
4
5
6
7
8
9
10
git log:
git reflog:只有HEAD有变化,就会记录下来

* 撤销上一次提交,上一次提交没用了
* git reset --soft HEAD~
只动HEAD指针(带着分支一起移动),不会变工作区和暂存区
* git reset [--mixed] HEAD~
只动HEAD指针(带着分支一起移动),会动暂存区
* git reset --hard HEAD~
只动HEAD指针(带着分支一起移动),会动暂存区,和工作区
1
2
3
4
5
checkout brancname 和 git reset --hard commithash
共同点:都重置 HEAD 暂存区,工作区
区别: checkout对工作区是安全的,hard是强制覆盖
checkout只移动HEAD
reset 会让HEAD和分支一起移动

打tag

1
2
3
4
5
6
7
8
9
10
11
12
列出tag
* git tag
* git tag -l v1.8.5*
创建标签
* git tag v1.0 标签是不会移动的
查看特定标签
* git show v1.0
删除标签
* git tag -d v1.0
检出标签
* git checkout v1.0 分离头指针的状态,必须创建分支
* git checkout -b "v1.0" 创建一个分支

远程仓库

主用户创建远程操作github仓库步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 先在github上创建一个空的仓库new repository 注意不要有readme.md文件
2. 创建本地仓库然后基础设置 git init
3. 然后给github上面的地址起别名和用户别名
* git remote add use(别名) https://github.com/xxx/git_to_use.git
* git config --list
4. 注意如果是复制别人的github 要把.git删掉只复制项目部分代码到自己的本地仓库
5. 注意凭据 本人是可以直接上传的需要配置用户名和邮箱
6. 检查完毕后推送到远地仓库 git push use(别名) master(分支)
7. 给员工开放权限通过github里面的manage access contributor
8. 获取员工上传的代码 git fetch use(别名)
9. 切换成远程跟踪分支 git checkout use/master
10. 合并远程跟踪分支 git merge use/master
11. git pull 获取数据并合并

拉取仓库代码

1
2
3
4
5
6
7
1. 本地不用创建仓库 直接克隆下来
* git clone https://github.com/xxx/git_to_use.git
2. 它自动创建一个别名; 查看别名git remote -v
3. 创建新的文件 echo "hello world">test.txt
4. git add ./
5. git commit -m "tijiao"
6. git push origin master

本地分支 远程分支 远程跟踪分支

  • 本地分支是本机电脑的
  • 远程分支是github上对应的分支
  • 远程跟踪分支是本地与远程分支的一个映射
1
2
3
4
5
6
7
8
* 成员克隆远程仓库以后默认本地分支和对应的远程跟踪分支有同步关系
* 在push的时候会生成对应的远程跟踪分支
* 在fetch的时候把数据下载到远程跟踪分支里面
* 注意成员开辟新分支提交的时候 经理在fetch的时候要创建对应的分支不用加别名
* 主分支和远程跟踪分支自动绑定的功能(默认情况下push的时候)
* 建立同步关系 git branch -u (远程跟踪分支) 注意要在那个分支里面输入这个命令
* git checkout --track remote别名/分支名 最自动创建本地分支并且与远程跟踪分支绑定
* git checkout -b 分支名 remote别名/分支名 效果与上面一样

一个本地分支这么跟踪一个远程分支

  1. 当克隆的时候,会自动生成一个master本地分支(已经跟踪了对于的远程跟踪分支)
  2. 当新建其他分支是,可以指定要跟踪的远程跟踪分支
    1. git checkout -b 本地分支 远程分支名
    2. git checkout –track 远程分支名
  3. 将一个已经存在的本地分支 改成一个跟踪分支
    1. git branch -u 远程跟踪分支

删除远程分支

1
2
3
* git push use(别名) --delete (分支名)  删除远程分支
* git remote prune use --dry-run 列出仍在远程跟踪但是远程分支已经被删除的无用分支
* git remote prune use 清除上面的命令列出来的远程跟宗

冲突

1
2
3
4
5
6
7
* git本地操作的冲突
* 典型合并的时候
* git远程协作的时候
* push
* 两个人同时推(更改同一个文件)解决办法只能先把远程仓库拉下来 然后再更改那个 文件然后再add commit push
* pull
* 更改完以后不push 直接pull会报错 远程仓库会覆盖更改的内容建议push 不过push还会出错就是上面那个错误

参加开源项目的步骤 (pull request)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
* 如果参加某个项目时,但是没有推送权限,这时候可以通过对这个项目进行fork。这会在你的空间中创建一个完全属于你的项目副本,且你对其具有推送权限。通过这个方式项目的管理者不用忙着添加贡献者,人们可以fork这个项目将修改推送到项目副本上,并通过pull 
request来将他们的改动进入源版本库

1. 先将源仓库fork到自己的仓库
2. 然后clone到本地仓库
3. 更改后提交到自己的远程仓库
4. pull request
5. 管理人审核 然后merge

* 不重新fork怎么解决
* git 支持同时跟踪多个仓库
git remote add 别名2 地址
<!-- git remote rm 别名1 -->
git fetch 别名2
git branch -u 远程跟踪分支
git merge 对应的远程跟踪分支
git push (这里还是会提交到自己上面)
然后pull request

5. IDEA git开发

  1. 在Version 中配置git

  2. VCS -> import into Version Control -> Create GitRepository ->选择要创建的仓库

    • 红色代码 工作区
    • 绿色代码 add 之后 暂存区
    • commit 之后到本地库中 ,变为正常颜色
  1. 创建仓库,将本地仓库push到远程仓库 Git -> Repository
  • Remotes 远程仓库的地址
    • Pull 拉取
    • Push 发送
  1. 其他人员clone 项目 VCS -> Checkout… ->填写地址

    1. 修改 -> add -> commit (如果要push到仓库需要权限)
  2. 如果代码发送冲突

    1. idea会直接拒绝,需要远程更改 Merge
    2. 修改冲突 Head表示最新版本 ====表示远程代码

Pro Git