版本控制工具入门——GIT

2019-07-18T22:43:00

GIT 与 SVN 的区别

            SVN 服务器
              ↗ ↑ ↖
             /  |   \
            /   |    \
           /    |     \
         ↙      ↓      ↘
SVN 客户端  SVN 客户端  SVN 客户端

SVN 是 集中式管理版本库 位于 SVN 服务器 上,优点是便于管理员掌控 开发进度,也容易给每个开发人员 授权。缺点是,服务器可能发生 单点故障,并且 容错性较差

                共享版本库
          ——————————————————————
         /   /   ↗      \   \   ↖
        /   /   /        \   \   \
       /   /   /          \   \   \
      /   /  Push         Clone\   \
     /  Pull /              \  Pull \
 Clone  /   /                \   \  Push
  ↙    ↙   /                  ↘   ↘   \
开发人员 ——                    开发人员 ——
↑         |                   ↑         |
|         |                   |         |
——————Commit                  ——————Commit

GIT 是 分布式版本控制系统,没有中央服务器,每个开发人员都 拥有完整的版本库,开发时无需联网,修改完毕后再提交给 共享版本库 即可。

简单来说,GIT 拥有 本地仓库,而 SVN 必须连接到远程仓库修改代码。

GIT 版本控制流程图

    ———————————————Pull(Fetch + Merge)—————————————
   |                                               ↓
远程仓库  ——Clone——→  本地仓库   ————Checkout——→  工作区
 Remote  ←——Push——  Repository                Workspace
                        ↑           暂存区          |
                     Commit———————  Index  ←——————Add
                                    Stage

GIT 常用命令详解

GIT 配置

同时操作 Github 和公司私有仓库需要配置不同的邮箱和私钥;Github 配置代理才可高速访问,公司不需要。以上需求都可通过 git config 预先配置。该命令有三个作用域选项,--system--global--local,分别用来对系统,全局和项目局部进行配置,优先级由低到高,默认 --local。除了使用命令,也可直接编辑配置文件,--global 对应 $HOME/.gitconfig--local 对应项目工作目录下的 .git/config

# 查看所有配置
git config --list

用户和密钥配置

配置密钥可避免每次提交输入密码。

# 生成多个密钥,-t(type),-C(comment),-f(file)
ssh-keygen -t rsa -C "[email protected]" -f ~/.ssh/id_rsa_github
ssh-keygen -t rsa -C "[email protected]" -f ~/.ssh/id_rsa_gitlab

# 将加载密钥脚本添加到 bash 启动文件
cat >> ~/.bashrc <<"EOF"
# 启动 ssh-agent 管理 ssh session
eval `ssh-agent -s` > /dev/null 2>&1
keys=(`ls ~/.ssh/*.pub | sed 's/.pub//g' | xargs`)
for key in ${keys[@]}
do
      ssh-add "${key}" > /dev/null 2>&1
done
EOF
cat >> ~/.bash_logout <<"EOF"
eval $(ps -ef | grep ssh-agent | grep -v grep | awk '{ print "kill "$2 }')
EOF

# 重启 bash
/usr/bin/env bash
ssh -T [email protected]

# 向全局配置文件添加不同 git 站点
cat >> $HOME/.ssh/config <<'EOF'
# gitlab
Host git.iboxpay.com
  HostName git.iboxpay.com
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_rsa_gitlab
  User admin

# github
Host github.com
  HostName github.com
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_rsa_github
  User admin
EOF

# 切换到每个项目目录,当独设置用户名和邮箱
git config user.email "[email protected]"
git config user.name "admin"

# 取消设置
git config --unset user.name
git config --unset user.email

代理配置

为 Github 设置代理可以加快代码同步速度,对大项目很有必要。

# 设置
git config --global http.https://github.com.proxy socks5://127.0.0.1:1080

# 取消设置
git config --global --unset http.https://github.com.proxy

初始化仓库

新建文件夹 repositories/repo1 作为工作区 Working Directory/Workspace。进入工作区,通过 git initgit clone 命令创建一个本地仓库/版本库或下载远程版本库,该操作将会在工作区初始化一个 .git 文件夹,存储 版本库

# 递归创建目录
mkdir -p repositories/repo1

# 进入目录
cd repositories/repo1

# 初始化本地仓库
git init

# 不创建工作目录/工作区,用作远端共享版本库
# git init --bare

# 下载远端仓库
# git clone ssh://[email protected]/home/git/repo1

# 指定远程分支
# git clone -b dev ssh://[email protected]/home/git/repo1

提交变更

版本库中最重要的是暂存区 Stage/Index,每次在工作区 新建、修改或删除 文件后,都要使用 git add 将变更添加到暂存区,随后使用 git commit 将暂存区中的变更提交到当前 HEAD 指针指向的分支。首次 commit 时,GIT 会自动创建第一个分支 master 和指向 master 的指针 HEAD,下面是工作区和版本库的示意图。

Workspace                   |                  Repository
dir1                        |                          HEAD
  |--file1                  |                              ↘
  |--file2                  | Stage                          master
  |--file3         add——→   | dir2                           dir1
  |--dir2        ↗          |   |--file1                       |--file1
       |--file4             |   |--file2                       |--fiel2
       |--file5             |              ↘                   |--file3
                            |                 commit——→        |--dir2
                            |                                       |--file4
                            |                                       |--file5
# 新建 HelloWorld.java 文件
cat > HelloWorld.java <<"EOF"
public class HelloWorld {

    public static void main(String[] args) {
        // Prints "Hello, World" to the terminal window.
        System.out.println("Hello, World");
    }

}
EOF

# 添加单个修改文件到暂存区
git add HelloWorld.java

# 添加所有修改文件
# git add .

# 提交变更到本地仓库
git commit -m "Initial commit."

# 查看提交记录
git log --oneline

# 撤销某次之后的所有提交
git reset <commit_hash>

commit message 包含 HeaderBodyFooter 三部分,一般仅使用 Header。按照 type(scope): subject 格式填写 Header 更有助于团队合作。scope 表示功能模块,subject 代表主题,type 为类型,一般定义如下:

  • feat:新功能
  • fix:修复 bug
  • style:更改格式
  • refactor:代码重构
  • chore:项目重建
git commit -m "fix(security): upgrade lodash.template"

辅助提交

nodejs 插件 commitizen,可以帮助你填入标准的 commit message,在此之前需要安装 nodejs,可以参考 该教程

# 安装 commitizen
npm install commitizen -g

# 使用 cz-conventional-changelog 包初始化 commitizen,
#+ 需要在项目仓库运行
npm init
commitizen init cz-conventional-changelog --save-exact

# 使用 git cz 代替 git commit 提交,之后根据提示填写 message
git cz

生成 Changelog

使用 conventional-changelog 插件,可以方便地生成标准 Changelog,默认根据 commit messagefeatfix 生成。

# 安装 conventional-changelog
npm install conventional-changelog -g

# 生成 Changelog,-p(preset),
#+ -r 0 从首次 commit 开始生成,覆盖之前的 CHANGELOG.md
conventional-changelog -p angular -i CHANGELOG.md -s -r 0

文件状态追踪

仓库中的文件分为 已追踪未追踪 两种。已追踪文件在版本库中存在记录,用户工作一段时间后,这些文件仍可被查看。下面是文件状态变更图。

sequenceDiagram
    Unmodified->>Modified: Edit the file
    Modified->>Staged: Stage the file(git add)
    Unmodified->>Untracked: Remove the file
    Untracked->>Staged: Add the file
    Staged->>Unmodified: Commit

通过 git status 可以查看到最近的文件状态变更,主要意义在于提醒开发者 commit 前应先 add,之后才可以 push

# 假设之前已经 commit 过
# 新增一个文件
touch newFile

# 修改一个文件
echo 1 >> oldFile1

# 删除一个文件
rm oldFile2

# 查看文件状态变更
git status

# newFile 属于 Untracked 文件
# oldFile1 属于 Modified 文件
# oldFile2 属于 Unstaged 中的 Deleted 文件

除了查看所有文件状态,通过 git diff 还可查看当前对某个文件的具体修改。

# 查看未暂存文件修改信息
git diff

# 查看已暂存文件修改信息
git diff --staged

忽略特定文件/文件夹

在某个目录新建 .gitignore 文件,并将需要忽略的文件/文件夹写入其中,即可在 git commit 时忽略它们,该文件的语法规则如下:

  • 每行一条规则
  • 空行用于增强可读性
  • # 开头的行将被当作注释,不参与解析
  • \ 开头的规则必须使用 \\
  • 行尾的若干空格将被忽略,除非使用 \ 注释每个空格
  • ! 前缀用于否定前面的规则,但对上级目录设定的规则无效,也不会对子目录中的文件生效,如文件以 ! 开头,要使用 \! 注释
  • 在名称后添加 / 解析为文件夹,如 foo/,表示忽略当前目录的 foo/ 及其子文件夹,不忽略 foo 文件
  • 不含 / 的规则会被当作 shell glob 匹配:* 匹配除了 / 的任何字符串,? 匹配除了 / 的单个字符,[] 匹配特定范围的单个字符
  • 行首的 / 用来避免递归,如 /*.c 匹配 cat-file.c,不匹配 mozilla-sha1/sha1.c
  • 与完整路径名匹配的连续两个 ** 可能有特殊意义:

    • ** 开头后跟 / 匹配所有路径下的文件。如 **/foo 匹配任何地方的 foo 文件或路径。**/foo/bar 匹配 foo 目录下任何地方的 bar 文件或路径
    • /** 结尾的规则匹配目录下的所有文件和路径,如 abc/** 匹配 abc 及其子目录下的所有文件
    • /**/ 匹配零个或多个目录。如 a/**/b 匹配 a/ba/x/ba/x/y/b 等等

操作远程仓库

远程仓库、origin、本地仓库、暂存区和工作目录的关系。

remote repository ——-
          |         |
git fetch |         | git pull
          ↓         |
origin(remote name) |
 local repository ←--
          ↑
git commit|
          |
        index
          ↑
  git add |
          |
  working directory
# 与多个远程仓库建立连接,origin 是远程仓库别名
git remote add origin ssh://[email protected]/home/git/repo0
git remote add origin1 ssh://[email protected]/home/git/repo1

# 删除与远程仓库的连接
git remote remove origin

# 查看所有 remote 地址
git remote -v

# 将本地分支推送至远程仓库的 master 分支
#+ -u | --set-upstream,设置默认提交上游,
#+ 执行一次后,之后提交直接 git push 即可
git push -u origin master
# git push -u origin1 master

# 删除远程分支
git push origin -d dev
# git push origin1 -d dev

# 推送 tag 到远程分支
git push --tag origin dev

处理冲突

假如你的协作者和你同时拉取最新版本代码并对同一文件进行修改,当你想把变更推送至远端,而他人先于你推送时便会发生冲突,此时需要使用 git mergegit rebase 手动合并你的变更,随后才能推送。GIT 会把文件中的冲突区域标记在 <<<<<<< HEAD>>>>>>> [other/branch/name] 之间,中间用 ======= 隔开。

使用 git merge 合并时,会一次性解决之前所有提交的冲突,而 git rebase 仅解决一次提交发送的冲突,这意味着开发者之后还要执行多次 git rebase --continue 操作。

# 保存本地代码
# git stash

# 使用 merge 合并
# git fetch
# git merge
git pull

# 使用 rebase 合并
# git rebase
# git rebase --continue

# 合并本地与远程
# git stash pop

# 冲突区域示意
cat conflictFile
<<<<<<< HEAD 
int a=1;
=======
int a= 0;
>>>>>>> master

冲突处理完毕后需要将其重新添加到暂存区,再提交到本地仓库,随后提交到远程仓库。

git add .
git commit -m "refactor(Login): Merge file"
git push

管理分支

开始时,HEAD 指针指向 master 分支,master 指向最新提交,,两个指针随着 commit 不断后移如此,如此就能确定当前分支和当前提交点。

               HEAD
                ↓
              master
                ↓
◯——————◯——————◯
v1      v2      v3

创建新分支并切换

一般 master 分支用于发布新版本,dev 分支用来开发,hotfix 分支用来修复 bug。创建新分支并切换时,HEAD 指针会执行它。

# 创建分支
# git branceh dev

# 切换分支
# git checkout dev

# 可合并为一条命令
git checkout -b dev
              master
                ↓
◯——————◯——————◯
v1      v2      v3
                  ↖
                    \
                    dev
                     ↑
                    HEAD

此时,如果再进行一次提交,master 仍会停留在原位置,dev 后移指向最新提交。

# 一些修改
# ...

# 提交变更
git commit -m "refactor(Login): change code structure"
              master
                ↓
◯——————◯——————◯
v1      v2      v3
                  \
                   \
                   ◯
                  dev1
                    ↑
                   dev
                    ↑
                   HEAD

合并分支

假设开发到 dev2 时已趋于稳定,计划合并到主分支,可通过 git mergegit rebase 进行分支合并,若使用前者,两个分支在合并后都会指向公共的提交;若使用后者,将被合并分支的提将被拷贝到当前分支上,被合并分支没有提交记录。

                                       HEAD
                                        ↓
                                      master
                                        ↓
◯——————◯——————◯——————◯——————◯——————◯
v1      v2      v3      v4      v5      v6
                  \                     /
                   \                   /
                   ◯                 /
                  dev1               /
                    |               /
                   ◯———————————————
                  dev2
                    ↑
                   dev
                [git merge]
--------------------------------------------
                [git rebase]
                                               HEAD
                                                ↓
                                              master
                                                ↓
◯——————◯——————◯——————◯——————◯——————◯——————◯
v1      v2      v3      v4      v5     dev1'   dev2'
                  \
                   \
                   ◯
                  dev1
                    |
                   ◯
                  dev2
                    ↑
                   dev
# 切换回最终分支
git checkout master
# git fetch orign master
# git pull 等于 git fetch + git merge

# 将 dev 合并过来
git merge dev
# git rebase dev

# 撤销合并
git merge --abort
# git rebase --abort

# 回滚到合并前状态
# git reset --hard
# git reset <commit_hash> --hard

删除分支

# 删除
git branch -d dev

# 查看所有分支
git branch

标签管理

标签一般用作版本号,打上的标签是固定的,不像分支那样可以移动位置。

               HEAD
                ↓
              master
                ↓
◯——————◯——————◯
|       |       |
v1      v2      v3
# 查看所有标签
git tag

# 查看某些标签,-l(list)
git tag -l v0.0.*

# 为当前提交加标签,-a(add) -m(message)
git tag -am "This is the first version." v1

# 为之前某次提交打标签
# 查看提交记录
git log --oneline
# 加上 commit hash 前几位(可区分即可)
git tag 4a48e8e5f60c -am "This is the first version." v0.9

# 删除标签,-d(delete)
git tag -d v1

# 推送标签
git push --tag

撤销和回滚

可以撤销 commit 之前和之后的变更,commit 之前的变更包括未进暂存区和已进入暂存区的更改。

# 修改文件
echo "new content" >> oldFile
git status

# 未进暂存区,使用 checkout 撤销
# 撤销单个文件
git checkout --oldFile
# 撤销所有文件
git checkout
git status

##############################
echo "new content" >> oldFile
git add .
git status

# 已进入暂存区,使用 reset HEAD 将其拉出
# 拉出单个文件
git reset HEAD oldFile
# 拉出所有文件
git reset HEAD
git status


##############################
echo "new content" >> oldFile
git add .
git commit -m "refactor[Login]: add some function"
git push
git log

# 撤销已有提交,revert 实际上是一次新的 commit
git revert <commit_hash>

# 再次 revert 又可恢复提交
git revert <revert_commit_hash>
git push

当需要回滚到某次提交时,可使用 get reset 命令,与 revert 不同,该操作不会生成提交记录,因此是不可逆操作,须谨慎使用。

git reset --hard <commit_hash>
git push --force

在 IDEA 中操作 GIT

添加工程到本地仓库

  • 依次进入菜单 File -> Settings -> Version Control -> Git,配置 git.exe 路径
  • 新建项目,依次进入菜单 VCS -> Import into Version Control -> Create Git Repository,选择项目上层目录
  • 此时面板上会出现 Git 菜单,点击 Commit 图标,选择需要提交的文件和文件夹,填入 message 提交即可

远程仓库的克隆和推送

  • 依次进入菜单 File -> New -> Project from Version Contrl -> Git,添加远程仓库地址
  • 测试 ssh 方式 Windows 下不可用,使用 https 方式,输入用户名密码确定即可克隆
  • 推送时,点击 Git 菜单的 Commit,选择 Commit and Push

操作分支

  • 依次进入菜单 VCS -> Git -> Branches,选择 New BranchCheckout Tag or Revision

完整项目示例

############## 项目创建 ##############
# 创建文件夹
mkdir repo

# 进入文件夹
cd repo

# 初始化仓库
git init

# 配置用户名
git config user.name "logi"

# 配置邮箱
git config user.email "[email protected]"

# 配置命令别名
git config --global alias.pull pl
git config --global alias.push ps
git config --global alias.commit cm
git config --global alias.merge mg
git config --list

# 添加 .gitignore 文件
cat > .gitignore <<'EOF'
# nodejs 相关
node_modules/
npm-debug.log*
yarn-debug.log*
npm-error.log*

# 编译后文件
/dist/

# 编辑器配置
.DS_Store
.idea
.vscode
*.suo
*.njsproj
*.sln
EOF

# 添加 README 文件
cat > README.md << 'EOF'
# Git 命令详解
EOF

# 进行首次提交
git add .
git commit -m "chore(all): initial project"

# 与远程仓库建立连接
git remote add origin ssh://[email protected]/home/git/repo

# 查看所有远程仓库
git remove -v

# 推送到远程仓库
git push -u origin master

############## 本地开发 ##############
# 建立新分支
git branch logi

# 切换到新分支
git checkout logi

# 在 logi 分支模拟开发
cat > index.js << 'EOF'
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
EOF

# 查看最近变更
git status

# 将变更加入暂存区
git add .
# 提交变更
git commit -m "feat(index): add project index"
# 推送到远程仓库
git push -u origin logi


############## 项目上线 ##############
# 切换到 master 分支
git checkout master

# 合并 logi 分支
git merge logi

# 推送到远端 master 分支
git push -u origin master

# 打 tag
git tag -a v0.1.0 -m "First version"

# 推送 tag 到远程分支
git push --tag origin master

############## 问题修复 ##############
# 建立新分支并切换
git checkout -b hotfix-718

# 模拟修复过程
echo >> index.js <<'EOF'
function hotfix718() { //... }
hotfix718;
EOF

# 提交代码
git add .
git commit -m "fix(index): fix ui bugs"
git push -u origin hotfix-415

# 切换回 master 分支
git checkout master
git merge hotifix-718
git push

# 打上新版本并提交
git tag -a v0.1.1 -m "Fix bugs"
git push --tag origin master

# 删除 hotfix 分支
git branch -d hotfix-718
git push origin -d hotfix-718

############## 继续开发 ##############
# 切换到个人分支 logi
git checkout logi

# 修改某个模块
echo > index.js <<'EOF'
// ...
EOF

# 发现忘记拉取 master 最新代码,
#+ 此时需要保存工作
git stash

# 拉取最新代码
git pull origin master

# 恢复暂存文件,之后可能需要处理冲突
git stash pop

# 提交修改
git add .
git commit -m "feat(index): add auth module"
git push

# 合并到 master 并推送
git checkout master
git merge logi
git push origin master

# 打上新版本并提交
git tag -a v0.1.2 -m "Fix bugs"
git push

参考文献

当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »