版本控制工具入门——GIT
GIT 与 SVN 的区别
SVN 服务器
↗ ↑ ↖
/ | \
/ | \
/ | \
↙ ↓ ↘
SVN 客户端 SVN 客户端 SVN 客户端SVN 是 集中式管理,版本库 位于 SVN 服务器 上,优点是便于管理员掌控 开发进度,也容易给每个开发人员 授权。缺点是,服务器可能发生 单点故障,并且 容错性较差。
共享版本库
——————————————————————
/ / ↗ \ \ ↖
/ / / \ \ \
/ / / \ \ \
/ / Push Clone\ \
/ Pull / \ Pull \
Clone / / \ \ Push
↙ ↙ / ↘ ↘ \
开发人员 —— 开发人员 ——
↑ | ↑ |
| | | |
——————Commit ——————CommitGIT 是 分布式版本控制系统,没有中央服务器,每个开发人员都 拥有完整的版本库,开发时无需联网,修改完毕后再提交给 共享版本库 即可。
简单来说,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 "admin@gmail.com" -f ~/.ssh/id_rsa_github
ssh-keygen -t rsa -C "admin@163.com" -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 git@github.com
# 向全局配置文件添加不同 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 "admin@gmail.com"
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 init 或 git clone 命令创建一个本地仓库/版本库或下载远程版本库,该操作将会在工作区初始化一个 .git 文件夹,存储 版本库。
# 递归创建目录
mkdir -p repositories/repo1
# 进入目录
cd repositories/repo1
# 初始化本地仓库
git init
# 不创建工作目录/工作区,用作远端共享版本库
# git init --bare
# 下载远端仓库
# git clone ssh://git@192.168.1.254/home/git/repo1
# 指定远程分支
# git clone -b dev ssh://git@192.168.1.254/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 包含 Header,Body 和 Footer 三部分,一般仅使用 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 message 的 feat 和 fix 生成。
# 安装 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/b,a/x/b,a/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://git@192.168.1.254/home/git/repo0
git remote add origin1 ssh://git@192.168.1.254/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 merge 或 git 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 merge 和 git 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 Branch或Checkout Tag or Revision
完整项目示例
############## 项目创建 ##############
# 创建文件夹
mkdir repo
# 进入文件夹
cd repo
# 初始化仓库
git init
# 配置用户名
git config user.name "logi"
# 配置邮箱
git config user.email "logi@logi.im"
# 配置命令别名
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://git@192.168.1.254/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参考文献
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »