Git快速上手指南

我司最近搭建了内网服务器,以后再做项目时都要用git来进行版本控制了,我在这里把一些常用的操作写下来方便查阅。

安装Git

Windows:Git安装包/绿色版可以在官网自行下载,安装包与绿色版不同的是绿色版需要自己手动把GitBinaryPath/bin添加到系统的Path路径。

Linux(debian):sudo apt-get install git

初次设置

安装完之后启动git.exe(windows),Linux直接在终端输入:

1
2
$ git config --global user.name "Your Name"
$ git config --global user.email "[email protected]"

生成SSH Key(邮件地址换成你自己的邮件地址):

1
$ ssh-keygen -t rsa -C "[email protected]"

然后一路Enter(或者你自己设置密码),执行完毕后会在C:\Users\${userName}\(linux则是在~/下)下创建一个.ssh目录,其中有id_rsaid_rsa.pub两个文件。

id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

然后在远程Git上(Github、GitLab)上将你的SSH Key添加到你的账户中。

因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

创建本地仓库

启动Git Bash然后cd(或者使用Easy Context Menu将GitBash添加到系统右键菜单)到要创建本地仓库的目录执行以下命令:

1
2
// 初始化一个目录
$ git init

本地提交文件

1
2
3
$ git add ${file_name}
// commit
$ git commit -m "commit message"

添加远程仓库

1
2
3
4
5
// 添加一个远程仓库(${repo_name}可以自定义)
$ git remote add ${remote_repo_name} [email protected]:ayconanw/test-ue4.git

// example
$ git remote add origin [email protected]:ayconanw/test-ue4.git

删除远程仓库

1
2
3
4
5
// 如果想要移除本地仓库关联的远程仓库,可以执行下面的命令
$ git remote rm ${remote_repo_name}

// example
$ git remote rm origin

推送到远程仓库

1
2
3
4
// ${branch}默认为master(主分支)也可以自己修改(不建议)。-f为忽略冲突强制提交。
$ git push ${remote_repo_name} ${branch}
// 从远程仓库拉取更新,参数同上
$ git pull ${remote_repo_name} ${branch}

推送到多个远程仓库

一步推送至两个或多个远程仓库有两种办法。
第一种是是用git的set-url --add命令:

1
2
3
4
# 添加第一个远程仓库
git remote add blog [email protected]:imzlp/blog-source.git
# 添加第二个远程仓库,重复向同一个远程仓库名字添加需要set-url --add参数
git remote set-url --add blog [email protected]:visoinsmile/blog.git

第二种方法是直接编译.git/config文件,将多个远程仓库地址添加至同一个远程仓库的名字:

1
2
3
4
5
6
7
8
$ nano .git/config

# 编辑.git/config,填入多个仓库地址
[remote "blog"]
url = [email protected]:imzlp/blog-source.git
url = [email protected]:visoinsmile/blog.git

$ git push blog

新建分支

1
2
3
4
5
6
7
8
9
10
11
// -b是base的意思,就是以当前分支为基础创建一个新分支 
$ git branch ${new_branch_name} -b
// 切换分支
$ git checkout ${new_branch_name}

// 也可以使用git checkout命令加上-b参数表示创建并切换
$ git checkout -b ${new_branch_name}

//相当于以下两条命令:
$ git branch ${new_branch_name}
$ git checkout ${new_branch_name}

checkout有三种用途:切换分支、移除修改、从历史版本中签出,后面会写到。

1
2
3
4
5
# 从远程的origin/level为基础创建本地分支
$ git checkout -b level origin/level
# 上面的命令等同于下面两条
$ git checkout -b origin/level
$ git branch -m level

推送本地当前分支到远程分支

1
2
// 若远程不存在${new_branch_name}分支则会自动创建
$ git push ${remote_repo_name} ${new_branch_name}

合并分支

1
2
3
4
5
// 首先需要切换回需要合并到的分支,比如将dev分支合并到master分支
$ git branch master
// 将dev分支合并到当前分支
$ git merge dev
// 合并完毕后就可以删除dev分支

取消合并

如果本地与远程分支(或本地分支)有冲突可以取消合并,使本地分支回到合并之前的状态。

1
$ git merge --abort

解决冲突

在git中,如果是文本文件有冲突可以直接在pull时来diff区别,但是如果git管理的是二进制文件就不那么方便了。

如果想要取消本地的某次提交${123456}的更改可以使用回退到历史版本,然后使用:

1
2
3
4
5
$ git reset --hard 123455
# 将123456提交的文件签出到123455的提交中去
# 相当于直接在123455的基础上做了123456中的文件修改
$ git checkout 123456 .
# 然后在reset/checkout不想提交的文件即可

二进制文件的冲突

在Git里面文本文件的的合并可以直接看到,但是二进制的会很蛋疼。

如果我们要保留远程分支的git.exe可以执行以下命令:

1
2
# 保留远程版本的git.exe
$ git checkout --theirs git.exe

而保留本地分支的文件命令为:

1
$ git checkout --ours git.exe

然后再执行:

1
2
3
$ git add git.exe
# 假如上面执行的是 --theirs
$ git commit -m "合并冲突,保留远程版本"

此时本地就与远程合并了,并且合并时保持了远程的文件版本。

删除分支

1
2
3
4
5
6
7
8
// 删除本地分支
// 注意:删除分支之前要合并分支,不然会报错(或者使用-D强制删除)
// error: The branch 'dev' is not fully merged.
// If you are sure you want to delete it, run 'git branch -D dev'.
$ git branch -D dev

// 删除远程分支
$ git push ${repo_name} --delete ${remote_branch_name}

撤销删除的分支

两种情况

  1. 已经退出 Terminal

git reflog 查看你上一次 commit SHA1

gitReflog

1
$ git branch ${branch_name} ${SHA1}

就可以根据 你的SHA1值,创建一个分支,这个commit 你可以选择删除分支操作的 commit SHA1

  1. 没有退出Terminal

删除分支的时候会有SHA1

1
2
$ git branch -d dev
Deleted branch dev (was ffe7c46).

然后利用这个SHA1值可以恢复删除的分支

1
$ git branch ${branch_name} ffe7c46

版本回退

1
2
// 查看所有提交的版本
$ git log --pretty=oneline

gitlog

1
2
// 回退到某一版本
$ git reset --hard 版本号

gitReset

1
2
// 回退某个文件到某一版本
$ git reset 版本号 filename

撤销未提交的文件修改

只撤销本地修改

假如现在我们对分支内的一些文件进行了一些修改,且未进暂存区(git add)。

修改文件后,使用 status 命令查看一下文件状态

gitStatus

Git 提示我们,对于未 add 进暂存区的文件,可以使用 git checkout -- 快速撤销本地修改。

1
2
// 快速撤销本地修改
$ git checkout -- ${filename}

同时撤销本地和暂存区修改

那么,对于已 add 进暂存区的文件,如何撤销本地修改?还是先使用 status 命令查看一下文件状态:

gitStatus2

可以先取消暂存:

1
$ git reset ${filename}

gitResetFile

再取消本地修改:

1
2
// 快速撤销本地修改
$ git checkout -- ${filename}

一步到位的快速撤销修改

1
$ git checkout HEAD -- ${filename}

gitCheckoutRestFile

从历史版本中签出文件

有时候我们提交了新的版本,但是感觉某些文件的变动不太合适,想要将某几个文件回退到之前的版本,其余的保持新版本的状态,可以使用checkout命令。

1
2
3
4
5
6
7
// 查看提交的历史版本
$ git log
// 将文件从历史版本签出到当前版本
$ git checkout 历史版本号 ${filename}

// 将file.txt从bde5455fd58079f66f10d1526a579cda2be38190版本中签出到当前版本
$ git checkout bde5455fd58079f66f10d1526a579cda2be38190 file.txt

后续就可以像普通的文件一样使用add来提交了。

重命名本地分支

1
git branch -m ${CurrentBranchName} ${NewCurrentBranchName}

克隆远程仓库

1
2
3
4
5
// clone仓库所有分支
$ git clone ${remote_repo}

// 只clone一个分支可以使用-b指定分支名字
$ git clone -b ${branch_name} ${remote_repo}

切换到远程分支

远程仓库 git clone 下来后,执行 git branch,只能看到:

1
* master

并不会看到其他分支,即便远程仓库上有其他分支。

可以使用git branch -va首先列出本地/远程分支列表:

1
$ git branch -va

gitBranchVA

切换到远程origin/master分支:

1
$ git checkout remotes/origin/master

gitCheckoutRemoteBranch

还需要进一步操作:

1
2
$ git checkout -b ${remote_branch_to_local_branch_name}
// e.g. git checkout -b dev

-b 的意思是base,以当前分支为base,新建一个名叫 dev 的分支,这里当然也可以使用其他的命名。此时再执行 git branch -va 就能看到:

gitBranchVA2

就 OK 了~

然后在本地修改了dev分支之后可以直接推送到远程dev分支:

1
$ git push origin dev

从远程仓库clone特定分支

1
$ git clone -b ${OriginRepoBranchName} ${OriginRepoAddr}

举个栗子:

1
git clone -b master [email protected]:imzlp/blog-source.git --depth=2

即从远程仓库拉取master分支,--depth指定的是拉取历史版本的深度。。

忽略不必要提交的文件

有时我们不想要把所有的东西都提交到仓库中(比如一些编译生成的二进制文件),可以使用.gitignore来排除不需要提交的文件。
在Windows下我们不能够直接创建这个文件,因为是.开头没有文件名,所以需要在GitBash下使用命令创建:

1
$ touch .gitignore

然后编辑.gitignore在里面添加自己需要排除的文件/目录即可。

1
2
3
4
5
6
# 排除当前目录下的public文件夹
public/
# 排除所有的.exe文件
*.exe
# 排除所有的txt文件
*.txt

关于.gitignore的更多内容可以看这里——忽略特殊文件 - Git教程

比较不同版本的差异

1
2
3
4
5
6
7
8
9
10
11
12
# 查看尚未暂存的文件更新了哪些部分
$ git diff
# 查看尚未暂存的某个文件更新了哪些
$ git diff filename
# 查看已经暂存起来的文件和上次提交的版本之间的差异
$ git diff –cached
# 查看已经暂存起来的某个文件和上次提交的版本之间的差异
$ git diff –cached filename
# 查看某两个版本之间的差异
$ git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c
# 查看某两个版本的某个文件之间的差异
$ git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename

两个branch之间的diff

常见的diff用法是比较工作区,index,HEAD或者是diff两个提交。除了这些用法之外diff还可以比较2个分支,用法如下:

1
2
3
$ git diff topic master (1)
$ git diff topic..master (2)
$ git diff topic...master (3)

用法1,直接跟两个使用空格分隔的分支名会直接将两个分支上最新的提交做diff,相当于diff了两个commit。
用法2,用两个点号分隔的分支名,作用同用法1(装酷利器)
用法3,用三个点号分隔的分支名会输出自topic与master分别开发以来,master分支上的change。
需要注意的是这里的..和...不能与git rev-list中的..和...混淆。

错误处理

bad signature

如果使用git终端时出现以下错误:

1
2
3
$ git status
error: bad signature
fatal: index file corrupt

这是由于索引损坏造成的,可以通过下面的方式来处理:

1
2
rm -f .git/index
git reset

更多详情请看stack overflow上的讨论:How to resolve “Error: bad index – Fatal: index file corrupt” when using Git

关闭Git的SSL验证

1
$ git config --global http.sslVerify false

私钥权限过于开放报错

在使用Git时有时会遇到这样的提示(通常是机器间共享私钥的时候)。

1
2
3
4
5
6
7
8
9
10
11
12
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/root/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/root/.ssh/id_rsa": bad permissions
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

这是提示你的Git私钥读写权限过于开放会有安全问题(我这里是644),解决方案就是把私钥(id_rsa)的读写权限改为600。Linux的读写权限可以看工具、环境的知识收录:Linux文件权限修改

1
sudo chmod 600 ~/.ssh/id_rsa

Windows下的解决办法:
对文件.ssh/id_rsa点右键属性-安全/高级,禁用继承,将文件所有者更改为你自己的账户,然后添加你自己的账户和SYSTEM完整的控制权,保存即可。
参考文章: Windows SSH: Permissions for 'private-key' are too open

Git更新远程分支列表

1
$ git remote update origin --prune

submodule

如果再一个git仓库中又用到其他git仓库的,就可以使用git module来处理这个包含关系。

如果直接在git仓库A中把另一个仓库B clone下载作为A的子目录,在git add时会有下列类似的提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint: git submodule add <url> Plugins/VaRest
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint: git rm --cached Plugins/VaRest
hint:
hint: See "git help submodule" for more information.

目的就是告诉你你包含了另一个git的仓库。

正确的处理办法是,先把之前clone的B删除,然后使用git submodule来clone B:

1
2
3
4
5
6
7
8
9
10
$ git submodule add [email protected]:ufna/VaRest.git Plugins/VaRest
Cloning into 'C:/Users/imzlp/Documents/Unreal Projects/GWorld/Plugins/VaRest'...
remote: Enumerating objects: 123, done.
remote: Counting objects: 100% (123/123), done.
remote: Compressing objects: 100% (81/81), done.
remote: Total 1796 (delta 74), reused 83 (delta 42), pack-reused 1673
Receiving objects: 100% (1796/1796), 624.66 KiB | 44.00 KiB/s, done.
Resolving deltas: 100% (955/955), done.
warning: LF will be replaced by CRLF in .gitmodules.
The file will have its original line endings in your working directory

会把B clone下来,然后进到B的目录里面,将版本记录改为你想要的commit,然后再add/commit即可。

1
2
3
4
5
6
$ git status
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: Plugins/VaRest (new commits)

然后再push到远程仓库即可,在远程仓库中,A项目中的B目录就不是直接上传的文件,而是连接到真正的B的源仓库。

初始更新子模块

1
$ git submodule update --init --recursive

后续更新子模块

1
2
3
$ git submodule foreach git fetch
# 或
$ git submoudule update --remote

删除子模块

删除一个以添加的子模块需要有以下几个步骤:

  1. 删除子模块的目录
  2. 删除.gitmodules中的关于该子模块的信息
  3. 删除.git/config中关于该子模块的信息
  4. 删除.git/modules下该子模块的目录

执行完上面几步操作之后就可以了,但是如果有报错问题可以删掉缓存:

1
$ git rm --cached 子模块名称

git gc

如果拉取时出现下面这样的提示:

1
2
Auto packing the repository for optimum performance. You may also
run "git help gc" manually. See "git help gc" for more information.

因为Git往磁盘保存对象时默认使用的格式叫松散对象(loose object)格式。Git时不时地将这些对象打包至一个叫 packfile的二进制文件以节省空间并提高效率。当仓库中有太多的松散对象则就会提示你运行git gc

所以出现上面的提示时,直接在终端运行:

1
$ git gc

等执行完毕就可以了。

从远程拉取分支

1
2
# 查看远程分支
$ git branch -r

方法一:

1
2
# 拉取远程分支并创建本地分支
$ git checkout -b ${LocalBranchName} origin/${RemoteBranchName}

方法二:

1
$ git fetch origin ${RemoteBranchName}:${LocalBranchName}

fatal: The remote end hung up unexpectedly

将postBuffer改大一点就可以(以字节为单位):

1
$ git config http.postBuffer 524288000

Git开启大小写敏感

Windows上的Git默认是大小写不敏感的,可以通过修改配置实现:

1
$ git config core.ignorecase false

Git设置代理

1
2
git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

取消git代理:

1
2
git config --global --unset http.proxy
git config --global --unset https.proxy

创建本地空仓库

1
$ git --bare init

这个命令是创建一个空仓库,可以在其他的项目中将该仓库添加为远程仓库。


Early EOF Error

1
2
3
4
5
6
7
8
[email protected] ~
$ git clone -v git://192.168.8.5/butterfly025.git
Cloning into 'butterfly025'...
remote: Counting objects: 4846, done.
remote: Compressing objects: 100% (3256/3256), done.
fatal: read error: Invalid argument, 255.05 MiB | 1.35 MiB/s
fatal: early EOF
fatal: index-pack failed

解决办法:关闭core.comparession

1
git config --global core.compression 0

找不到仓库

如果拉取时提示:

1
2
3
$ git.exe pull--progress-v--no-rebase "origin"
remote: The project you were looking for could not be found.
fatal: repository "https://192.168.2.223/gyvr/GWorldS1g. git/' not found

在确认服务器连通正常,并且SSH Key无问题之后,可以使用下列方法解决。

这个问题的原因是之前这台电脑上使用过其他的git账号,并且在电脑中保存了密码,当请求远程仓库时会默认使用保存过密码的账号,如果请求服务器的账号对仓库没有访问权限则会产生这个报错。

那么解决的办法就是在控制面板中删掉多余的凭据:

之后重新Clone或者拉取会提示输入git账号和密码。

Export Diiff files

有时候需要知道两个版本之间更新了哪些文件信息,可以使用git diff命令,但是也不想知道具体更改的细节,只是需要之后某些文件被改动了,可以使用下面的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git diff COMMIT_HASH_1...COMMENT_HASH_2 --name-only > diff.txt 
Config/DefaultEngine.ini
Content/Core/BP_GameMode.uasset
Content/Maps/GameMap.umap
Content/Maps/GameMap_BuiltData.uasset
Content/Maps/LoginMap.umap
Content/Maps/LoginMap_BuiltData.uasset
Content/Pak/Cube.uasset
Content/Pak/Mats/BasicShapeMaterial_Blue.uasset
Content/Pak/Mats/BasicShapeMaterial_White.uasset
Content/Pak/Mats/BasicShapeMaterial_Yellow.uasset
Content/UI/MountPak.uasset
GWorld.uproject

就会列出所有变动的文件名了,关键点就在于--name-only参数。

PS:COMMIT_HASH_1...COMMENT_HASH_2这个必须是老版本在前,新版本在后。

列出commit列表

不同于直接git log,我只想要简单地列出每一个提交的记录而不关心提交者、时间、等等。

可以使用git的占位符来:Git Basics - Viewing the Commit History

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log --pretty=format:"%h \"%s\""
fee8b06 "fix word error"
84a2a8b "Base v0.0.1"
91a9991 "set default map"
5e2499e "add download pak test"
cbcbb9a "add UI"
ca34d8f "fix bugs"
589cf38 "fix bug"
9e9ca9b "add CreateFileByBytes"
64d3b94 "添加测试文档"
52b7562 "enable VaRestPlugin"
5e6adc0 "add VaRest/General Lib"
9562bf8 "set GWorld Project to mobile"
3f16e75 "GWorldSlg init"

--pretty=format:支持的更多的选项:

OptionDescription of Output
%HCommit hash
%hAbbreviated commit hash
%TTree hash
%tAbbreviated tree hash
%PParent hashes
%pAbbreviated parent hashes
%anAuthor name
%aeAuthor email
%adAuthor date (format respects the --date=option)
%arAuthor date, relative
%cnCommitter name
%ceCommitter email
%cdCommitter date
%crCommitter date, relative
%sSubject

rebase

如果只是使用merge来合并分支,那么会从目标分支合并过来所有的commit信息和提交历史,有时候我们想要以一个功能的版本来作为一个commit,而不是每次修改一点点就作为一次commit,这样git的history会有很多无意义的commit,怎么解决这个办法呢?那就是使用rebase。

首先说一下rebase的流程:

  1. 在分支A的基础上创建分支B:git checkout -b B
  2. 然后在分支B上尽情修改和提交
  3. 当在分支B上的功能做的差不多了,想要同步会分支A时就可以使用rebase来折叠commit

具体操作为:

在分支B上找到从分支A切过来的基础版本commit的hash信息,然后使用下列命令:

1
2
3
4
# 指定rebase到的基础版本
$ git rebase -i 0fee16a1a2f20d299c678e9846ef720c2ca228ad
# 或者指定从当前HEAD往上查找几个commit
$ git rebase -i HEAD~3

此时会弹出vim来编辑界面,里面列出了所有B分支从基础版本到HEAD的commit,默认都是pick的,但是因为需要折叠commit,所以我们需要把多个commit合并,需要使用git提供的squash命令:

  • pick:保留该commit(缩写:p)
  • reword:保留该commit,但我需要修改该commit的注释(缩写:r)
  • edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
  • squash:将该commit和前一个commit合并(缩写:s)
  • fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
  • exec:执行shell命令(缩写:x)
  • drop:我要丢弃该commit(缩写:d)

只保留一个pick,其他的都替换为squash即可。然后会弹出commit的编辑界面,可以把不想要的commit信息使用#注释掉,然后保存退出即可,rebase就完成了。

  1. 最后一步是切回A分支,执行merge操作:git merge B,merge完之后在A分支就只会看到一次commit.

参考文章

Git远程操作详解
创建与合并分支
Git撤销分支删除操作
git checkout 命令撤销修改
git diff之diff两个分支

全文完,若有不足之处请评论指正。

扫描二维码,分享此文章

本文标题:Git快速上手指南
文章作者:ZhaLiPeng
发布时间:2016年09月29日 22时19分
更新时间:2018年08月15日 00时57分
本文字数:本文一共有5.2k字
原始链接:https://imzlp.me/posts/53696/
许可协议: CC BY-NC-SA 4.0
捐赠BTC:1CbUgUDkMdy6YRmjPJyq1hzfcpf2n36avm
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!