Git

175 阅读7分钟

什么是版本控制?

版本控制是指对软件开发过程中各种程序代码、说明文档等文件的变更进行管理,它将追踪文件变化,记录文件的变更时间、变更内容、甚至变更执行人进行记录,同时对每一个阶段性变更(不仅仅只是一个文件的变化)添加版本编号,方便将来进行查阅特定阶段的变更信息,甚至是回滚

什么是 Git?

人工版本控制器

通过人工的复制行为来保存项目的不同阶段的内容,添加适当的一些描述文字加以区分

  • 繁琐、容易出错
  • 产生大量重复(冗余)数据

版本控制工具

通过程序完成上述人工版本控制行为

  • 方便且功能强大
  • 只记录不同版本之间变化的部分

常见版本控制工具

  • CVS
  • SVN
  • Git
  • ……

怎么工作的?

首先,我们得先了解两个重要概念

  • 状态
  • 区域

git 文件生命周期

lifecycle.png

状态

同时,git 又提供了三种(也可以说是四种)不同的记录状态

  • 已修改(modified)
  • 已暂存(staged)
  • 已提交(committed)

有一个特殊的状态

  • 未追踪(Untracked) git add是将未跟踪(Untracked)或已修改文件(Modified)放入暂存区(Staged):

区域

git 提供了三个不同的工作区,用来存放不同的内容

  • 工作目录
  • 暂存区域
  • Git 仓库

areas.png

区域与状态对应

  • 工作目录 -> 已修改(modified)
  • 暂存区域->已暂存(staged)
  • git仓库 -> 已提交(committed)

安装

git-scm.com/

配置

当安装完 Git 应该做的第一件事就是设置你的用户名称与邮件地址。 这样做很重要,因为每一个 Git 的提交都会使用这些信息,并且它会写入到你的每一次提交中,不可更改

git config user.name "你的姓名"
git config user.email "你的邮箱"

-- global

通过 --global 选项可以设置全局配置信息

git config --global user.name "你的姓名"
git config --global user.email "你的邮箱"

### 检查配置

```bash
# 打印所有config
git config --list
# 打印指定config
git config user.name

创建仓库 - repository

进入希望纳入 git 版本控制的项目目录,使用 git init 初始化

git init

该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这个目录也是上面我们说的三个区域之一,这个目录也是 Git 保存数据记录的地方,非常重要,如非必要,不要轻易改动

工作流与基本操作

当一个项目被 Git 初始化以后,只是表示我们希望通过 Git 来管理当前的这个项目文件的不同时期版本记录,但是这个时候项目中已存在的文件,或者以后新增的文件都是没有进入版本控制管理的,它们是 未追踪(Untracked) 的状态

查看工作区的文件状态

git status

git status

查看工作区中的文件状态

git status -s

??未追踪文件

A 添加到暂存区文件

   M被修改未被放入暂存区

M   修改了并提交到暂存区

MM修改后放入暂存区,并且又再次修改

D 文件被删除 

R 重命名

快速看到文件当前的命令

乱码

git status 显示乱码

git config --global core.quotepath false

终端乱码

菜单 -> 设置 -> 文本 -> 本地 / 编码

或修改配置文件

[gui]  
    encoding = utf-8  
    # 代码库统一使用utf-8  
[i18n]  
    commitencoding = utf-8  
    # log编码
[svn]  
    pathnameencoding = utf-8  
    # 支持中文路径
[core]
    quotepath = false 
    # status引用路径不再是八进制(反过来说就是允许显示中文了)

添加工作区文件到暂存区

git add

git add 1.txt
# 添加多个文件
git add 2.txt 3.txt
# 添加整个目录
git add ./a
# 添加多个目录
git add ./b ./c
# 添加所有文件
git add .

创建版本

git commit

将暂存区里的改动给提交到本地 git 仓库,也就是为这次工作(一般会把某个具有特定意义的工作作为一个版本,它可以是多个文件的变化)

  • 每次提交同时会生成一个 40 位的哈希值,作为该次提交版本的唯一 id

提交备注

每次提交都需要填写备注信息

git commit
# 会调用默认(或自定义)的文本编辑器

修改默认编辑器

git config core.editor notepad

# 添加 vscode 编辑器 - mac
# 通过 vim 打开环境变量配置文件
vim ~/.bash_profile
# 添加环境变量
export PATH=/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin:$PATH
# 保存退出
source ~/.bash_profile
# 测试:在终端中直接通过命令 code 调用 vscode
git config --global core.editor "code --wait"

单行备注

git commit -m 备注信息

查看提交日志

git log

// 完整格式
git log
// 简要格式(单行)
git log --oneline

修复提交

git commit --amend

修复(替换上一次)提交,在不增加一个新的提交版本的情况下将新修改的代码追加到前一次的提交中

git commit --amend -m 提交

删除

git rm

# 从 git 仓库与工作区中删除指定文件
git rm 文件

# 只删除 git 仓库中的文件不删除目录中,提交中不希望有某一个文件
git rm --cached 文件

# rm 以后,需要 commit 这次操作,否则 rm 将保留在暂存区
git commit -m 修正

# 创建文件直接删除会报错
#强制删除,注意不要随便强制删除 导致有些修改没有提交就删除 暂存信息有问题
git rm -f 文件 

撤销重置

git reset

从暂存区中撤销到工作区

// 从暂存区中撤销一个指定文件
git reset HEAD 文件名称
// 从暂存区中撤销所有文件
git reset HEAD .

该命令既可以用于回退版本

# 回退到指定的 commitID 版本
git reset --hard commitID

移动文件

常用情况为重命名和移动文件

git mv file_from file_to

mkdir first 新建名为 first的文件夹

git mv a.txt first/a.txt

mv 修改的文件名称 修改完的文件名称(加上这步操作才会认为是修改名称)

git rm 文件名 

git add 新文件名

重命名了

git commit

比较

# 比较 工作区和暂存区
git diff 文件 
# 比较 暂存区和仓库
git diff --cached [commitId] 文件
# 比较 工作区和仓库
git diff commitId filename
# 比较 仓库不同版本
git diff commitId1 commitId2

分支

我们的开发就像是游戏的任务,默认是在主线(master)上进行开发的。许多时候,还有各种支线任务,git 支持我们创建分支来进行项目开发

某天完成了项目的一次开发后,准备开始二次开发,但是如果直接在一次开发的基础上更改,我们的文件就是1+2,管理起来相当不方便,这个时候考虑用分支来解决

查看分支

git branch

创建分支

git branch 分支名称

切换分支

git checkout 分支名称
# 也可以使用 checkout -b 来新建分支
git checkout -b 分支名称
#切换到master分支
git checkout master

分支合并

# B 合并到 A,需要切换到 A 分支
git merge 被合并分支

# 查看已经合并的分支
git branch --merged
# 查看未合并的分支
git branch --no-merged

#将banch1与master合并
git merge banch1

删除分支

# 如果分支为未合并状态,则不允许删除
git branch -d 分支名称
# 强制删除
git branch -D 分支名称

合并记录

rebase

# 合并 HEAD 前两个祖先记录
git rebase -i HEAD~2

~ 与 ^

~ : 纵向

^ : 横向

path.png

rebase 操作

# p, pick = use commit => 使用
# r, reword = use commit, but edit the commit message => 使用,但重新编辑说明
# e, edit = use commit, but stop for amending => 使用
# s, squash = use commit, but meld into previous commit => 使用,但合并上一次
# f, fixup = like "squash", but discard this commit's log message => 就像 squash 那样,但会抛弃这个 Commit 的 Commit message
# x, exec = run command (the rest of the line) using shell => 执行脚本
# d, drop = remove commit => 移除
git rebase -i HEAD~3
# 弹出编辑器,根据需要的进行修改,然后保存
# 如果为 r,s 则会再次弹出编辑器,修改新的 commit message,修改之后保存

如果出现一些问题,可以通过 git rebase --edit-todogit rebase --continue 进行重新编辑保存

合并冲突

有的时候,不同的分支可能会对同一个文件内容和位置上进行操作,这样在合并的过程中就会产生冲突

  • 查看冲突文件

  • 修复冲突内容

  • 提交 如何判断是否会引起冲突:

  • master和branch1处于同一条commit路径上(祖先级关系)

  • 是  master和branch1直接进行合并,不会引起冲突

  • 否 

    1. 分析master和branch1中的修改是否一致,一致,进行空合并
    2. 不一致。产生冲突

解决分支冲突:

1. 手动解决冲突部分
2. 解决完成后再次提交,以此次提交内容为准

取消合并

git merge --abort

撤销

撤销当前文件的修改 git chechout -- a.txt    (注意该修改必须是还没有加入暂存区的修改)

# 撤销上一次提交信息
git commit --amend
  1. 修改提交信息
  2. 修正紧挨着的一次的提交与本次提交合并
  3.  git commit -m '描述' --amend (amend撤销上一次提交的信息)
  4. git commit --amend 延用上一次的描述

# 取消暂存

  git reset HEAD
  git reset HEAD a.txt  (a.txt从暂存区取消)
  

标签

有的时候,我们希望给某一个特定的历史提交打上一些标签

新建 tag

git tag -a v1.0.0 HEAD/commitId

查看 tag

git tag

对比

对比工作区与暂存区的区别
git diff

添加的文件在第几行
@@ -1  +1,2@@

对比提交的与暂存区内的区别
git diff --staged

打印控制

git log -p 详细信息打印 打印最近一条同时打印diff

git log -2 打印两条信息

git log --pretty 设置打印内容的格式

  • oneline 哈希和描述
  • short 哈希、作者、描述
  • full 哈希、作者、提交者、描述
  • fuller 哈希、作者、日期、提交者、提交日期、描述
  • format 定制要显示的记录

此处的作者:实际修改的人

提交者:最后将此工作成果提交到仓库的人

git log --pretty=oneline打印哈希(注意左右两边不能存在空格)

git log --pretty=format:"%H %h"         

git log --stat 列出所有被修改的文件,以及简略的统计信息

firmat的常用选项

%H    提交对象commit的完整哈希字符
%h    提交对象的简短哈希字符
%T    树对象(tree)的完整哈希字符
%t    树对象的简短哈希字符
%P    父对象(parent)的完整哈希字符
%p    父对象的简短哈希字符
%an   作者(author)的名字
%ae   作者的电子邮件地址
%ad   作者修订日期(可以用--date=选项定制格式)
%ar   作者修订日期。按多久以前的方式显示
%cn   提交者(xommitter)的名字
%ce   提交者的电子邮件地址
%cd   提交日期
%cr   提交日期,按多久以前的方式显示
%s    提交说明

协同开发

以上所有的操作都是建立在本地的,如果我们希望进行团队协同开发,那么这个时候,我们就需要把 git 仓库信息与团队中的所有人进行共享

  • 分布式 - 中心化与去中心化

多人协作的工作模式通常是这样:

  • 首先,可以试图用git push origin branch-name推送自己的修改;
  • 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  • 如果合并有冲突,则解决冲突,并在本地提交;
  • 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
  • 如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,
  • 用命令git branch --set-upstream branch-name origin/branch-name。
  • tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

github

首先注册一个账号

使用 ssh 链接

SSH

help.github.com/cn/articles…

help.github.com/cn/articles…

生成 SSH 秘钥

ssh-keygen -t rsa -C "zmouse@miaov.com"

添加代理

使用 ssh-add 代理,如果没有启动,可以手动启动

eval $(ssh-agent -s)

添加 私钥

ssh-add 私钥路径

在 github 上添加公钥

个人中心 -> 设置 -> ssh -> 添加

测试

ssh -T git@github.com

git 远程

链接

git remote add origin git@github.com:miaov-zmouse/kkb-test.git

提交(同步)远程

同步本地仓库到远程

git push -u origin master
# -u 简化后续操作
git push origin master

远程分支

# 提交到远程(分支)
git push origin [本地分支名称]:[远程分支名称]

# 远程先创建好分支然后拉取到本地
git checkout -b [本地分支名称] origin/[远程分支名称]

# 拉取远程分支到本地
git pull origin [远程分支名称]:[本地分支名称]

# 查看远程仓库
git remote show origin

# 查看本地分支
git branch

# 查看远程分支
git branch -r

# 查看所有分支
git branch -a

# 删除本地分支
git branch -d [本地分支名称]

# 删除远程分支
git push origin --delete [远程分支名称]
# or
git push origin :[远程分支名称]

# 设置默认提交分支
git branch --set-upstream-to=origin/[远程分支名称] [本地分支名称]

扩展:工作流 - git work flow

gitflow.png

GUI 工具

git-scm.com/download/gu…

  • Sourcetree
  • other editor

常用git命令

操作命令
创建文件夹mkdir  
touch 多个用空格隔开\
清空: cls
进入文件夹:   cd
显示当前目录: pwd
把当前目录变成Git: git init
显示隐藏目录: ls -ah
查看git仓库状态:  git status
把文件添加到git仓库: git add
把文件提交到仓库:  git commit -m "说明"
查看修改后的不同: git diff
查看提交历史: git log --pretty=oneline
回退到上一个版本: git reset --hard HEAD^
回退到某个版本: git reset --hard
查看命令历史: git reflog
工作区和暂存区对比: git diff
暂存区和分区对比:git diff -- cached
工作区和分支对比: git diff -- <file...>
丢弃工作区的修改: git checkout -- <file...>
撤销暂存区的修改: git reset HEAD <file...> + git checkout -- <file...> 或 git reset --hard HEAD
删除工作区文件: rm <file...>
从版本库删除该文件: git rm <file...> + git commit -m "说明"
撤销工作区文件的删除: git checkout -- <file...>
撤销暂存区文件的删除: git reset HEAD <file...> + git checkout -- <file...> 或 git reset --hard HEAD
创建SSH Key: ssh-keygen -t rsa -C "15521232672@163.com"
关联一个远程库: git remote add origin git@github.com:RaymondHww/learngit.git
推送master分支的内容: git push -u origin master (第一次推送要参数 -u ,之后就不用了)
从远程克隆仓库到本地: git clone git@github.com:RaymondHww/gitskills.git
查看分支: git branch
创建分支: git branch
切换分支: git checkout
创建+切换分支: git checkout -b
合并某分支到当前分支: git merge
删除分支:  git branch -d
查看分支合并情况: git log --graph --pretty=oneline --abbrev-commit
使用普通模式合并: git merge --no-ff -m "说明" dev
把工作现场储藏: git stash
查看储藏的工作现场: git stash list
恢复储藏的工作现场: git stash apply stash@{0}
删除stash内容: git stash drop stash@{0}
恢复并删除stash内容: git stash pop
强行删除未合并的分支: git branch -D <branch_name>
显示详细的远程库信息: git remote -v
推送分支到远程库: git push origin master 或 git push origin dev
克隆远程库到本地后只有master分支
在本地创建和远程分支对应的分支: git checkout -b branch-name origin/branch-name
建立本地分支和远程分支的关联: git branch --set-upstream branch-name origin/branch-name
打一个新标签到最新的提交上: git tag v1.0
打一个新标签到历史的提交上: git tag v1.1
查看所有标签: git tag
查看标签信息: git show v1.0
创建带有说明的标签: git tag -a v1.2 -m "说明"
通过-s用私钥签名一个标签: git tag -s v1.3 -m "说明"
删除本地标签: git tag -d v1.0
推送某个标签到远程: git push origin
一次性推送全部尚未推送到远程的标签: git push origin --tags
删除已经推送到远程的标签: git tag -d v1.0 然后 git push origin :refs/tags/v1.0