你不知道的Git(助力开发☞开发手册)

150 阅读7分钟

前言

今天分享一个我平时用的git工具手册,助力开发,喜欢的可以点赞+收藏
tip:篇幅较长可根据目录直达

1. Git介绍

1.1 Git特点

Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。

  优点

  • 适合分布式开发,强调个体;
  • 公共服务器压力和数据量都不会太大;
  • 速度快、灵活;
  • 任意两个开发者之间可以很容易的解决冲突;
  • 离线工作。

  缺点

  • 代码保密性差,一旦开发者把整个库克隆下来就可以完全公开所有代码和版本信息;
  • 权限控制不友好,如果需要对开发者限制各种权限的建议使用SVN。

1.2 Git和SVN对比

Git是分布式的,SVN是集中式

这是 Git 和 SVN 最大的区别。若能掌握这个概念,两者区别基本搞懂大半。因为 Git 是分布式的,所以 Git 支持离线工作,在本地可以进行很多操作,而 SVN 必须联网才能正常工作。

SVN版本控制示意图:

image.png  

 

Git版本控制示意图:

image.png  

 

Git复杂概念多,SVN简单易上手*

所有同时掌握 Git 和 SVN 的开发者都必须承认,Git 的命令实在太多了,日常工作需要掌握add,commit,status,fetch,push,rebase等,若要熟练掌握,还必须掌握rebase和merge的区别,fetch和pull的区别等,除此之外,还有cherry-pick,submodule,stash等功能,仅是这些名词听着都很绕。

  在易用性这方面,SVN 会好得多,简单易上手,对新手很友好。但是从另外一方面看,Git 命令多意味着功能多,若我们能掌握大部分 Git 的功能,体会到其中的奥妙,会发现再也回不去 SVN 的时代了。

 

Git分支廉价,SVN分支昂贵*

在版本管理里,分支是很常使用的功能。在发布版本前,需要发布分支,进行大需求开发,需要 feature 分支,大团队还会有开发分支,稳定分支等。在大团队开发过程中,常常存在创建分支,切换分支的需求。

Git 分支是指针指向某次提交,而 SVN 分支是拷贝的目录。这个特性使 Git 的分支切换非常迅速,且创建成本非常低。

而且 Git 有本地分支,SVN 无本地分支。在实际开发过程中,经常会遇到有些代码没写完,但是需紧急处理其他问题,若我们使用 Git,便可以创建本地分支存储没写完的代码,待问题处理完后,再回到本地分支继续完成代码。

1.3 Git核心概念

Git 最核心的一个概念就是工作流

工作区(Workspace)是电脑中实际的目录。

暂存区(Index/ stage)类似于缓存区域,临时保存你的改动。

仓库区(Repository),分为本地仓库和远程仓库。

1.4 Git常用操作

image.png

1.4.1 配置

Git 用户的配置文件位于 ~/.gitconfig

Git 单个仓库的配置文件位于 ~/$PROJECT_PATH/.git/config  

--global:当前用户所有仓库生效,--local:当前Git仓库生效

列举所有配置

$ git config -l

为命令配置别名

$ git config --global alias.co checkout
 
$ git config --global alias.ci commit
 
$ git config --global alias.st status

$ git config --global alias.br branch

设置提交代码时的用户信息

$ git config [--global] user.name "[name]"

$ git config [--global] user.email "[email address]"

设置Git编辑器

$ git config --global core.editor nano/vim/gedit/emac

取消Git配置

$ git config –-unset [--global] core.editor user.name

1.4.2 初始化

--recurse-submodules:递归下载所有子模块

--branch:下载指定分支的代码

# 在当前目录新建一个Git代码库
$ git init

# 推送现有文件夹以初始化远端空仓库

$ cd existing_folder

$ git init --initial-branch=master

$ git remote add origin your_url

$ git add .

$ git commit -m "Initial commit"

$ git push -u origin master

 

# 推送现有的 Git 仓库到远端空仓库

$ cd existing_repo

$ git remote rename origin old-origin

$ git remote add origin your_url

$ git push -u origin –all (git push –u origin local_branch:remote_branch)

$ git push -u origin –tags

 

# 下载一个项目和它的整个代码历史(Git only)

$ git clone [--recurse-submodules] [url] [--branch branch_name]

1.4.3 增删文件

把文件名或者目录添加到 .gitignore 文件里,Git 会停止跟踪其状态。

# 添加当前目录的所有文件到暂存区
$ git add .

# 添加指定文件到暂存区
$ git add <file1> <file2> ...

# 添加指定目录到暂存区,包括其子目录
$ git add < dir >

# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

1.4.4 分支

# 列出所有本地分支
$ git branch
​
# 列出所有本地分支和远程分支
$ git branch –a
​
# 列出所有本地分支及其追踪的远程分支
$ git branch –vv

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]# 新建一个分支,并切换到该分支,该分支追踪remote**-**branch
$ git checkout -b [new_branch] [remote-branch]# 切换到指定分支,并更新工作区

$ git checkout [branch**-**name]# 合并指定分支到当前分支

$ git merge [branch]# 选择一个 commit,合并进当前分支

$ git cherry-pick [commit]# 删除本地分支,-D 参数强制删除分支

$ git branch -d [branch-name]# 删除远程分支

$ git push [remote] : [remote-branch](git push origin --delete [remote _branch])

1.4.5 提交

# 提交暂存区到仓库区

$ git commit -m [message]# 提交工作区与暂存区的变化直接到仓库区

$ git commit -a

​
# 提交时显示所有 diff 信息

$ git commit -v

​
# 提交暂存区修改到仓库区,合并到上次修改,并修改上次的提交信息

$ git commit --amend -m [message]# 上传本地指定分支到远程仓库

$ git push [remote] [remote-branch]

 
# 上传本地指定分支作为远程仓库指定分支

$ git push [remote] [local-branch]:[remote-branch]

1.4.6 拉取

# 下载远程仓库的所有变动 (Git only)

$ git fetch [remote]

​
# 显示所有远程仓库 (Git only)

$ git remote -v

​
# 显示某个远程仓库的信息 (Git only)

$ git remote show [remote]

​
# 增加一个新的远程仓库,并命名 (Git only)

$ git remote add [remote-name] [url]

​
# 取回远程仓库的变化,并与本地分支合并,(Git only), 若使用 Git-SVN,请查看Git-SVN章节

$ git pull [remote] [branch]

​
# 取回远程仓库的变化,并与本地分支变基合并,(Git only), 若使用 Git-SVN,请查看Git-SVN章节

$ git pull --rebase [remote] [branch]

1.4.7 撤销(不影响未追踪文件)

# 恢复暂存区的指定文件到工作区

$ git checkout [file]

​
# 恢复暂存区当前目录的所有文件到工作区

$ git checkout .

​
# 恢复工作区到指定 commit

$ git checkout [commit]

​
# 重置暂存区的指定文件,与上一次 commit 保持一致,但工作区不变

$ git reset [file]

​
# 重置暂存区与工作区,与上一次 commit 保持一致

$ git reset --hard

​
# 重置当前分支的指针为指定 commit,同时重置暂存区,但工作区不变

$ git reset [commit]

​
# 重置当前分支的HEAD为指定 commit,同时重置暂存区和工作区,与指定 commit 一致

$ git reset --hard [commit]

​
# 新建一个 commit,用于撤销指定 commit

$ git revert [commit]

​
# 将未提交的变化放在储藏区

$ git stash


​# 将未提交的变化放在储藏区

$ git stash list


# 将储藏区的内容恢复到当前工作区

$ git stash pop

1.4.8 清理未追踪文件

# 显示将要删除的文件

$ git clean –nd


# 删除指定文件

$ git clean –f [path]

 
# 删除所有未追踪文件和目录

$ git clean –df

 
# 删除所有未追踪文件和目录(包括被.gitignore忽略的文件和目录)

$ git clean –xdf

1.4.9 查询

# 查看工作区文件修改状态

$ git status               

​
# 查看工作区文件修改具体内容   

$ git diff [file]# 查看暂存区文件修改内容

$ git diff --cached [file]# 查看版本库修改记录

$ git log                  

​
# 查看某人提交记录

$ git log --author=someone# 查看某个文件的历史具体修改内容

$ git log -p [file]        

​
# 查看某次提交具体修改内容

$ git show [commit]

1.4.10 Git-SVN

# 下载一个 SVN 项目和它的整个代码历史,并初始化为 Git 代码库

$ git svn clone -s [repository]# 查看当前版本库情况

$ git svn info# 取回远程仓库所有分支的变化

$ git svn fetch# 取回远程仓库当前分支的变化,并与本地分支变基合并

$ git svn rebase# 上传当前分支的本地仓库到远程仓库

$ git svn dcommit# 拉取新分支,并提交到远程仓库

$ svn copy [remote_branch] [new_remote_branch] -m [message]# 创建远程分支对应的本地分支

$ git checkout **-**b [local_branch] [remote_branch]

 

1.5 Git Submodule 介绍

Git submodule允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。当进入子模块目录后,你就进入了子模块的本地仓库,在此目录的所有的修改和提交都是在对子模块的Git仓库进行操作。

image.png

1.5.1 新增Git Submodule

# 添加Submodule

$ git submodule add [-b branch] <remote_url> [destination_folder]

执行新增submodule指令时,Git会在执行如下动作:

1、 将目标Git仓库clone到你指定的路径

2、 在当前仓库创建.gitmodules文件,用于保存和管理submodule的远端地址

3、 在当前仓库的.git/config中添加submodule配置

4、 目标submodule和.gitmodules将被添加到暂存区等待提交

此时你可以查看submodule的状态

# 查看Submodule状态

$ git submodule

  使git status命令支持submodule状态显示

# git status展示Submodule状态

$ git config --global status.submoduleSummary true

  使git diff命令支持submodule日志显示

# git status展示Submodule状态

$ git config --global diff.submodule log

  接着,你需要将其提交到Git仓库

# 提交Submodule并推送远端仓库

$ git commit -m "Added the submodule to the project."

git push

1.5.2 拉取和更新Submodule

当clone包含有submodule的项目时,你需要在指令中使用--recurse-submodules参数。这样项目中的子模块才会被拉取到本地,否则你将得到一个空的目录。

# 完整clone包含Submodule的项目

$ git clone --recurse-submodules [url] [--branch branch_name]

  如果clone主项目时未使用--recurse-submodules参数,可以执行如下命令拉取submodule。--recursive参数用于递归对submodule的submodule执行操作。

# 拉取Submodule

$ git submodule update --init --recursive


# 等同于以下命令

$ git submodule init

$ git submodule update --recursive

上面这两种方式拉取的是Git主仓库中保存的submodule仓库的指定提交,当然你也可以拉取submodule仓库的最新提交。使用--merge或--rebase参数将会在更新submodule时执行合并动作,当然你也可以进入submodule目录,按照常规的Git操作进行代码更新和合并。

# 拉取Submodule最新提交

$ git submodule update -–remote [--merge/--rebase]

使用不包含--merge或--rebase参数的submodule update指令拉取或者更新submodule后,submodule仓库处于 “游离的 HEAD”的状态(detached the HEAD)。这意味着没有本地工作分支(例如 “master”)跟踪改动,所以你在submodule中做的任何改动都不会被跟踪。

 

1.5.3 提交submodule

提交submodule首先要确保submodule仓库处于本地工作分支状态。要切换到本地工作分支状态,你可以依次进入所有submodule目录,执行Git切换分支指令。也可以使用遍历submodule的指令foreach来切换分支。

# 遍历切换submodule分支

$ git submodule foreach [--recursive] git checkout [branch_name]

接着修改代码并提交到暂存区,最终推送到远端的submodule仓库。

# foreach遍历推送submodule更新到远端仓库

$ git submodule foreach [--recursive] git push [remote] [branch_name]


# 遍历推送所有submodule更新到远端仓库

$ git push --recurse-submodules = on-demand

最后,需要将新的submodule commit id推送到主仓库(即使你未对submodule代码做修改,只是同步到submodule仓库最新的提交,那么commit id也发生了变化。如果需要使用最新的代码,就需要推送submodule commit id推送到主仓库)。

# 更新主仓库submodule commit id

$ git commit –m "Submodule updated"

$ git push [remote] [branch_name]

 

1.5.4 遍历submodule

Git提供了submodule foreach命令,用于在所有submodule批量执行常规Git操作。

#批量在submodule仓库执行常用Git命令

$ git submodule foreach [--recursive] git push [remote] [branch_name]

 

1.5.5 删除submodule

# 删除submodule 

$ git submodule deinit <submodule>

$ git rm <submodule> 

$ rm -rf .git/modules/<submodule>

 

1.5.6 submodule注意事项

l 在父项目中git pull并不会自动更新子模块,需要调用git submodule update来更新子模块信息。如果忘记调用git submodule update,那么你极有可能再次把旧的子模块依赖信息提交上去。

l 调用git submodule update并不会将子模块切换到任何分支,默认情况下子模块处于“游离的 HEAD”的状态。如果此时我们改动子模块而没有检出一个工作分支,那调用git submodule update时你所做的任何改动都会丢失。

l Git子模块在父项目中维护所有依赖的子模块版本,当包含大量子模块时,父项目的更新将很容发生冲突,并且父项目的维护历史与所有子模块的维护历史相互交织,维护成本也会比较高。