Git

Git

Git is a distributed version control system.

Git is free software distributed under the GPL.

国内git仓库:Gitee.

初始设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
git --version
git update-git-for-windows

# 第一次使用git的初始设置:
git config --global user.name 'yourname'
git config --global user.email youremail

# Config levels: --system; --global; --local;

git config --global core.editor "'C:\Program Files\Sublime Text\sublime_text.exe'"

# 查看配置参数
git config --list

# 利用--help 参数查看帮助,例如:
git checkout --help

创建仓库

1
2
3
4
5
6
7
8
9
10
11
# 创建一个空白的仓库:如果不给仓库名,则将当前文件夹初始化为git仓库,默认创建分支为master
git init repository-name

# 克隆一个远程仓库到当前目录下
git clone remote_repository

# 克隆一个远程仓库到指定目录下(可以重命名)
git clone remote_repository <local_directory>

# cd进入仓库文件夹后可以查看仓库的状态,记录文件的增删改动作
git status

三个概念

  • 工作区
  • 暂存区
  • 版本库(.git):Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。

文件操作

  • 更改
    1
    2
    3
    4
    5
    6
    7
    8
    # 将文件加入仓库git(把文件修改添加到暂存区),实现版本跟踪
    git add filename

    # 将当前文件夹(工作区)内的文件加入仓库git,实现版本跟踪,.gitignore中的文件忽略
    git add .

    将文件移除仓库.git(取消暂存),不再跟踪
    git restore --staged filename
  • 提交

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 提交更改:把暂存区的所有内容提交到当前分支
    git commit -m 'message' filename

    # 提交所有更改:需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。Git跟踪并管理的是修改,而非文件。
    git commit -m 'message'

    # 查看提交日志(结果倒序排列)
    git log [--online]

    # 查看更改
    git diff filename

    # 查看工作区和版本库里面最新版本的区别
    git diff HEAD -- filename

  • 删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 从工作区和暂存区中删除文件
    git rm filename
    git commit

    # 如果误删了文件,所以可以从版本库中把误删的文件恢复到最新版本。
    git checkout

    # 其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
    # 命令git rm用于删除一个文件。
    # 如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。

    # 强制删除未提交的文件
    git rm -f filename

    # 删除整个仓库
    rm -rf .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
    # 放弃工作区的修改,让这个文件回到最近一次git commit或git add时的状态。
    git checkout -- filename

    # 把暂存区的修改撤销掉(unstage),重新放回工作区
    git reset HEAD filename

    # 回退到更旧的版本(满屏放不下的时候,就会显示冒号,英文状态下 按 q 可以退出git log 状态)
    git log [--pretty=oneline]
    git checkout 标识码(commit id至少6位)

    git checkout master (回到分支)


    # 在版本回退里,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。
    # HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,HEAD指向的版本就是当前版本,Git允许我们在版本的历史之间穿梭:
    git reset --hard commit_id
    git reset --hard HEAD^
    HEAD 表示当前版本
    HEAD^ 上一个版本
    HEAD^^ 上上一个版本
    HEAD~100

    # 穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
    # 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。

  • 标签

    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
    # 标签也是版本库的一个快照;tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

    # 首先,切换到需要打标签的分支上
    git branch
    git checkout master

    # 敲命令git tag <name>就可以打一个新标签
    git tag v1.0

    # 查看所有标签
    git tag

    # 默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?方法是找到历史提交的commit id,然后打上就可以了:
    git tag v0.9 f52c633

    # 标签不是按时间顺序列出,而是按字母排序的
    # 可以用git show <tagname>查看标签信息
    # 还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:

    git tag -a v0.1 -m "version 0.1 released" 1094adb

    如果标签打错了,也可以删除:git tag -d v0.1

    # 因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
    # 如果要推送某个标签到远程,使用命令
    git push origin <tagname>
    # 一次性推送全部尚未推送到远程的本地标签:
    git push origin --tags
    # 如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
    git tag -d v0.9
    # 然后,远程删除:
    git push origin :refs/tags/v0.9


    # 版本回退控制点tag:revisions
    git tag -a demotag1 -m "message"
    git tag -l
    git show demotag1
    git checkout demotag1
  • gitignore

    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
    60
    61
    # 在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。

    # .gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!

    # 如果有些文件已经被忽略了,当使用git add时是无法添加的。需要加上-f参数才能强制添加到git中去,这样就能强制添加到缓存中去了。

    # 如果我们意外的将想要忽略的文件添加到缓存中去了,我们可以使用rm命令将其从中移除

    # 示例

    # 表示此为注释,将被Git忽略
    *.a 表示忽略所有 .a 结尾的文件
    !lib.a 表示但lib.a除外
    /TODO 表示仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
    build/ 表示忽略 build/目录下的所有文件,过滤整个build文件夹;
    doc/*.txt 表示会忽略doc/notes.txt但不包括 doc/server/arch.txt

    bin/: 表示忽略当前路径下的bin文件夹,该文件夹下的所有内容都会被忽略,不忽略 bin 文件
    /bin: 表示忽略根目录下的bin文件
    /*.c: 表示忽略cat.c,不忽略 build/cat.c
    debug/*.obj: 表示忽略debug/io.obj,不忽略 debug/common/io.obj和tools/debug/io.obj
    **/foo: 表示忽略/foo,a/foo,a/b/foo等
    a/**/b: 表示忽略a/b, a/x/b,a/x/y/b等
    !/bin/run.sh 表示不忽略bin目录下的run.sh文件
    *.log: 表示忽略所有 .log 文件
    config.php: 表示忽略当前路径的 config.php 文件

    /mtk/ 表示过滤整个文件夹
    *.zip 表示过滤所有.zip文件
    /mtk/do.c 表示过滤某个具体文件

    被过滤掉的文件就不会出现在git仓库中(gitlab或github)了,当然本地库中还有,只是push的时候不会上传。

    需要注意的是,gitignore还可以指定要将哪些文件添加到版本管理中,如下:
    !*.zip
    !/mtk/one.txt

    唯一的区别就是规则开头多了一个感叹号,Git会将满足这类规则的文件添加到版本管理中。为什么要有两种规则呢?
    想象一个场景:假如我们只需要管理/mtk/目录中的one.txt文件,这个目录中的其他文件都不需要管理,那么.gitignore规则应写为::
    /mtk/*
    !/mtk/one.txt

    假设我们只有过滤规则,而没有添加规则,那么我们就需要把/mtk/目录下除了one.txt以外的所有文件都写出来!
    注意上面的/mtk/*不能写为/mtk/,否则父目录被前面的规则排除掉了,one.txt文件虽然加了!过滤规则,也不会生效!

    ----------------------------------------------------------------------------------
    还有一些规则如下:
    fd1/*
    说明:忽略目录 fd1 下的全部内容;注意,不管是根目录下的 /fd1/ 目录,还是某个子目录 /child/fd1/ 目录,都会被忽略;

    /fd1/*
    说明:忽略根目录下的 /fd1/ 目录的全部内容;

    /*
    !.gitignore
    !/fw/
    /fw/*
    !/fw/bin/
    !/fw/sf/
    说明:忽略全部内容,但是不忽略 .gitignore 文件、根目录下的 /fw/bin/ 和 /fw/sf/ 目录;注意要先对bin/的父目录使用!规则,使其不被排除。

分支

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
# 查看分支
git branch
# 查看本地和远程
git branch -a

# 新建分支
git branch branchname

# 跳转分支
git checkout branchname
git switch branchname

# 创建并跳转到分支
git checkout -b branchname
git switch -c branchname

# 分支重命名
git branch -M branchname

# 合并分支
git merge branchname
# 文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件.

# 查看分支合并图:
git log --graph

# 合并分支时,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
# 如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
# --no-ff参数,表示禁用Fast forward:
git merge --no-ff -m "merge with no-ff" dev
git log --graph --pretty=oneline --abbrev-commit

# 删除分支
git branch -d branchname

# 因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

远程仓库

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 本地Git仓库(master)和GitHub仓库(origin)之间的传输是通过SSH加密的,Git支持多种协议,包括https,但ssh协议速度最快。

# 链接本地仓库与远程仓库
git remote add origin git@github.com:michaelliao/learngit.git
# 远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。
git remote 返回远程库的名称origin,未连接远程库时没有输出

# 查看远程库信息查看远程库信息
git remote -v

# 操作远程库
git remote show origin
git remote add
git remote rm
git remote rename

# 推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上
# 把当前分支推送到远程库master分支
git push -u origin master

git push origin master
# 由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。


# 删除远程库:如果添加的时候地址写错了,可以删除远程库后重新连接。此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
git remote rm <name>


# git fetch 命令用于从远程获取代码库,该命令执行完后需要执行 git merge 远程分支到你所在的分支。
# git fetch 把远程提交拉取到本地仓库,而不是本地工作目录,它不会自行将这些新数据合并到当前工作目录中,我们需要继续执行git merge才会把这些变动合并到当前工作目录。
git fetch origin
git merge origin/master

# git pull直接获取远程的最新提交,直接拉取并合并到本地工作目录,而且在合并过程中不会经过我们的审查,如果不仔细检查,这样很容易遇到冲突。
# 相比之下,git fetch是一个更安全的选择,因为它从你的远程仓库拉入所有的提交,但不会对你的本地文件做任何修改。


# 首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
# 那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
# 你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
# 合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
# 软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。


# 首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:
git checkout master
git checkout -b issue-101
# 现在修复bug:修改,add,commit
# 修复完成后,切换到master分支,并完成合并,最后删除issue-101分支
git switch master
git merge --no-ff -m "merged bug fix 101" issue-101
git branch -D issue-101
# 原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到dev分支干活了
git switch dev

# Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
git status
# 工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看
git stash list
# 工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
git stash apply # 恢复后,stash内容并不删除,你需要用git stash drop来删除;
git stash pop # 恢复的同时把stash内容也删了
# 可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令
git stash apply stash@{0}

# Git专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支
git branch
git cherry-pick id


# 修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
# 当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场;
# 在master分支上修复的bug,想要合并到当前dev分支,可以用git cherry-pick <commit>命令,把bug提交的修改“复制”到当前分支,避免重复劳动。


# 开发一个新feature,最好新建一个分支;
# 如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>强行删除。

master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。


先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送

因此,多人协作的工作模式通常是这样:
首先,可以试图用git push origin <branch-name>推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!
如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>。
这就是多人协作的工作模式,一旦熟悉了,就非常简单。

这就是rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。


在GitHub上,可以任意Fork开源仓库;
自己拥有Fork后的仓库的读写权限;从作者仓库Clone的仓库,没有推送修改的权限。
可以推送pull request给官方仓库来贡献代码。

其他配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
git config --global color.ui true

# 设置别名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.unstage 'reset HEAD'
git config --global alias.last 'log -1'
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中
别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。
而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中