solomonxie / blog-in-the-issues

A personalised tech-blog, notebook, diary, presentation and introduction.
https://solomonxie.github.io
65 stars 12 forks source link

Gitflow #29

Open solomonxie opened 6 years ago

solomonxie commented 6 years ago

主要记录Git从零开始学习的一些常用流程和指令

Online Courses

solomonxie commented 6 years ago

❖ Git Workflow 1: Beginner's flow

最简单的Git工作流--即给初学者的工作流,尽量避免多分支,现在master分支上把常用指令学明白,然后再开启分支合流模式。

第一步 建立仓库 (Init | Clone)

一般会提到git init这个指令,在本地某个文件夹执行它就会把这个文件夹建立成一个git项目。但是我们初学者一般不是这个流程,我们需要建立一个github的repo,并将本地和它联通,反而简单很多。 首先直接到github首页新建一个repo,建好了以后直接点clone按钮复制.git结尾的链接。在本地用git clone命令克隆到本地生成一个文件夹项目。如果本地已经做了一些文件,那就把文件复制进这个文件夹就好了。 命令如下:

$ git clone 项目克隆网址 本地路径

然后进入文件夹开始项目即可。

第二步 本地提交 (commit)

先不涉及远程repo仓库,git需要在本地完成提交,常规三步如下:

# 查看本地文件变动状态
$ git status
# 添加变动文件到预备区
$ git add --all
# 完成提交
$ git commit -m "变动描述"

然后本地的准备就完成了,随时可以连接远程仓库。

第三步 远程提交 (push)

一般情况下,远程仓库都是我们自己的,拥有所有权限,所以暂不涉及向其他人的仓库提交(pull request)一类概念。 所以只需推送到远程自己的仓库,一句话git push即可。 然后如果在安装git后设置过通用的用户名和邮箱,这里就只会要求你输入密码,然后就可以上传本地提交到远程repo仓库里了。 就这么简单。前三步基本流程如下图: image

第四步 远程抓取 (pull)

有的时候会用别的机器(比如公司)提交一些变化到远程,然后回家后想把变化同步到本地。 如果远程也是自己的repo拥有完全权限,那么直接git pull即可完成一切同步。

solomonxie commented 6 years ago

初次运行Git的配置

Linux和Mac的git安装都很方便很多是自带的,Windows则强烈推荐安装Git Bash整个终端,包括了git本身以及所有常用的linux指令。

初始配置不是必要的。 因为主要是配置通用的用户名和邮箱,方便每个项目登录github并上传用的。简单就是两句话设置好:

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

这两句话就会分别设置好当前电脑用户的通用github(或其他服务商)登录名和邮箱,密码不在这里写是要在用的时候才在命令行里输入。

其实我要将的不是这两句话,网上很多人说过。只是要讲其实用git config命令的作用,只是把内容帮你加到~/.gitconfig这个文件里而已。虽然效果一样,但是便于日后管理所有git的配置内容,强烈建议不用命令输入,而是直接到这个文件里面改。参考下图:

这个是默认状态下的配置文件: image

这个是利用git config命令后的文件: image

所以为了日后方便管理,还是直接改文件的好。 另外,详细初始配置,如默认编辑器是vim还是emacs,differ用哪个编辑器显示等细节,参考git官方文档。起步 - 初次运行 Git 前的配置

solomonxie commented 6 years ago

.gitignore文件

整个项目里面肯定会有这个文件的,用来屏蔽一些文件的记录和上传。比如一些临时文件夹和涉及隐私的文件,就不需要传到github上了。

语法

简单说起来就是:

引用一个网上文章的例子:

# 忽略 .a 文件
*.a
# 但否定忽略 lib.a, 尽管已经在前面忽略了 .a 文件
!lib.a
# 仅在当前目录下忽略 TODO 文件, 但不包括子目录下的 subdir/TODO
/TODO
# 忽略 build/ 文件夹下的所有文件
build/
# 忽略 doc/notes.txt, 不包括 doc/server/arch.txt
doc/*.txt
# 忽略所有的 .pdf 文件 在 doc/ directory 下的
doc/**/*.pdf

再加一篇网上文章:彻底理解.gitignore

gitignore失效?已经被tracked文件的处理

一般gitignore教程都只会解释,当文件是untracked状态下,是怎么被git给ignore掉的。 但是他们很少提到,如果我有些文件已经被tarck了且已经有了commit历史了,怎么去ignore?

比如我有个settings-local.ini曾经被提交上去,但是发现这是个只需要本地存在而不想公开的文件,那么怎么ignore它? 如果你试过就知道,不管在.gitignore中怎么配置,都屏蔽步不掉它!换了很多种语法都是一样的结果,还以为是自己没写好gitignore。

实际上,要屏蔽已被追踪的文件,需要一步特殊处理:

$ git rm --cached path/to/file

# or folder
$ git rm -r --cached path/to/folder

参考:https://stackoverflow.com/questions/1274057/how-to-make-git-forget-about-a-file-that-was-tracked-but-is-now-in-gitignore

清除掉cache后,git会显示你已删除了这些文件(但实际上本地还存在),然后提交变动。这样git就再也感知不到它的存在了。

solomonxie commented 6 years ago

提交错误 error: There was a problem with the editor 'vi'.

在Mac上,可能之前重装vim变动了一些设置,所以才会有这个错误,导致git不能提交。 image 查了后解决方案很简单,直接输入:

git config --global core.editor $(which vim)
solomonxie commented 6 years ago

Git为每个repo设置不同的用户名

之前讲过在电脑本地文件夹中~/.gitconfig文件即可设置本机的通用用户名,用来登录远程。 但我常会把公开代码上传到github,私密代码上传到Bitbucket,所以需要不同的登录名等。 Git其实可以为每个repo设置单独的用户名用来登录远端,像global的用法一样,可以命令行里设置也可以直接在文件里写:

$ git config user.name "John Doe"
$ git config user.email johndoe@example.com

手动改写文件的话,就位于repo目录中的.git/config这个文件。 设置的格式和~/.gitconfig完全一样,具体参考git初始配置那篇。

solomonxie commented 6 years ago

Git误删文件恢复的方法

Stackoverflow的这个回答相当不错,我也很快找回了文件。参考链接 具体说起来是这样的,

# If the deletion has not been committed, 
# the command below will restore the deleted file in the working tree.
git checkout -- <file>

# You can get a list of all the deleted files in the working tree using the command below.
git ls-files --deleted

If the deletion has been committed, find the commit where it happened, then recover the file from this commit.
git rev-list -n 1 HEAD -- <file>
git checkout <commit>^ -- <file>
solomonxie commented 6 years ago

❖ 阿里云Code (Git托管)的初始设置

最近在考虑有的时候想托管一些国内方便的内容比如当cdn使用,还有码云什么的,比较了下易用性和容量,仅有的几个托管商里也就阿里云最合适,也最接近github。 链接 其它都是正常操作,唯一遇到的问题是git push的时候总是无法完成用户认真,在git里面设置了user.name和email等都不行。 原来阿里云code的登录密码是单独设置的,不能直接用自己的账号密码去登录, 必须要在阿里云Code的管理后台里面找到密码设置,然后选择忘记密码(修改密码没用,因为没有原始密码,只能点忘记密码)。

发邮件确认后,设置密码,就可以登录了。 所以这里git push时,用户名是阿里云的账号email, 密码是Code的单独密码。 参考官方文档

但是!这时候还不能做push等远程操作。如图: image

repo页面里面,一不小心漏过了一条提示: image

按照提示,在Profile->sshkeys页面里面,把本地电脑里~/.ssh/id_rsa.pub这个公钥文件内容粘贴到里面,添加密钥: image

阿里云还需要设置每个参与人员的权限。在控制台里可以找到,如下图: image

但是不管我怎么尝试,都添加不了members,项目中都members总是为0。

最后结果还是不能push。

solomonxie commented 6 years ago

Github 存储容量标准

官方解释的非常清楚,在这里,包括各种访问方式、大文件处理方式、repo存储空间提示等等的情况。 说白了,Github几乎没有任何容量限制,但是你不怀好意地把github当网盘用,频繁访问、不断传大文档等,是会收到通知的。

solomonxie commented 6 years ago

❖ Git文件历史追踪问题

有很多时候,git追踪某一个文件的变化历史和轨迹,是非常重要的,不管是代码还是文档。这里主要说文档。但是我们的笔记、文档,总是喜欢改名和转移目录之类行踪不定,那么一旦有这些改动,git还能追踪吗?

文件改名 「git rename」

git仓库实际上对文件改名这个问题是比较忌讳的,应当说是尽量避免的。因为如果不用特殊处理,改名之后git只会认为你删了这个文件,又新建了另一个文件。

如果你在系统里面手动把一个文档1.txt改成了2.txt,那么这时候输入git status只会看到1.txt被删除,新增加文件2.txt。 同样的,命令行里用mv 1.md 2.md改名,也是同样的效果。 要保持这个文件历史记录不断的话,正确做法是:

git mv 1.txt 2.txt

这样再用git status查看就会看到,识别为renamed: 1.txt -> 2.txt。它的历史记录就没有断。

文件移动 「git mv」

有时候会把文件移动到另一个文件夹或者某个子文件夹,但是git却认为你是删了一个而新建了另一个。

这是因为和改名一样,只要是在git命令之外移动的,git就识别不到。 所以,这里也要用git mv命令来移动。

git mv 1.txt ./src/1.txt

这个时候在查看git状态就会发现,显示为renamed: 1.txt -> src/1.txt

文件覆盖

这个问题逻辑要复杂点:有时候我们需要批量抓取网上的一些资源到这个目录里面,有些是重复的内容一样的,有些却是新的,还有些是网上删除了的,那么我们想要网上抓取的和本地的同步,应当怎么办呢?

虽然这个方法不严谨,但是非常简单有效:只要这些东西体积不是很大,那么就可以完全删除本地现有的文件,然后再把新抓取的保存到本地。 这个时候就是考验git的追踪识别能力了。 经过一系列试验发现:

solomonxie commented 6 years ago

❖ Git缓冲区理解:index,add和reset,staged和unstaged

在git里面,有一个叫index的区域,你把东西加到那里叫add, 把东西再从哪里撤回来叫reset;已经在里面的我们形容它是staged,还没有加进去的我们形容它是unstaged

其实index区就是一个纯粹的缓冲区,也叫staging area,是正式提交之前给我们的一个缓冲,还有犹豫的余地。因为一旦正式commit提交了,你所有还没解决的愚蠢的傻事都会公开,即使能覆盖能撤销,但还是掩藏不了历史。

自己做的话无所谓其实,但是如果是团队合作的话,每次commit都是一次公开。 其实形容的话,就相当于老板让你做个项目,你肯定不可能做了一点东西就跑到老板办公室去送一趟文件,应该会先把做好的放在桌子的上那个小文件架上。然后那个文件架就叫index

参考:Git 基础 - 撤消操作

撤销add

# 指定文件
$ git reset HEAD file.txt

# 全部撤销
$ git reset HEAD .

撤销修改

# 指定文件
$ git checkout -- file.txt

# 全部撤销
$ git checkout -- .

删除commit

一旦commit,就不能撤销!会永远留在历史里面。

修改commit

一般流程如下:

$ git commit -m '首次提交'

$ git add forgotten_file
$ git commit --amend

恢复某个文件到以前版本

# 用git log得知某个版本SHA后,恢复readme.md这个文件
$ git reset <SHA> readme.md

# 切换到该版本
$ git checkout readme.md

# 把变动提交
$ git add . && git commit
solomonxie commented 6 years ago

image

image

solomonxie commented 6 years ago

❖ Git的merge理解

一般来说,merge是新手的噩梦 。所以为了还有学习的动力,我前期几乎放弃,只是一心只用最简单的功能,等像现在这样慢慢理解了基本东西了,了解基本知识局限性了遇到很多问题了,才是好机会来加强理解这一层。

当我们谈到merge的时候,实际上是有很多种不同的情况的。比如有这几种情况的merge,分别是:local merge,sync mergefork merge。(这都是我自己起的名字)

Merge本身并不难理解,无非是从这边融合到那边,不同的只是起点终点的方向罢了。 Merge的难点在于merge conflict,就是融合的时候有冲突怎么办?

solomonxie commented 6 years ago

❖ Git入门之形象化理解checkout

git checkout实际上其实是个平行宇宙时光机,可以带你穿梭到任意一个平行宇宙中,还可以带你穿梭回过去的任意一个时间点。在过去的那个点上,你可以各种观察、修改、删除等,而不对原本时间点产生任何影响。

每一个branch分支,都是一个平行的宇宙,你可以用checkout在两个宇宙之间穿梭。 每一个commit提交,都是现在时间轴上的一个时间点,你可以用checkout回到过去的任何一个时间上。

顺着时光机的思路,你现在身处的时间点,在git里叫HEAD,而当你回到过去时,你的时间点就叫做detached HEAD,因为你已经是"detached reality"脱离现实了。

再来让它容易记一点,我们可以叫checkout为一个Jumper! 它可以跳来跳去,跳到任何地方。你可以用它来跳到别的分支,还可以跳到过去的任何点,总之git里面的它都可以跳过去。所以每次我们用git checkout时,我们可以心里念git 跳到,这样就好理解多了!

如果是git checkout 时间点,那么这就是一个回到过去的跳跃; 如果是git checkout 宇宙名,那么这就是一个平行宇宙的跨越。

其中时间点,就是每次commit的sha值,可以在git log中看到; 宇宙名,指的就是每个branch的名字,可以在git branch中看到。

那么如果我checkout跳到过去,还改变了些东西,会发生什么?

可以肯定的是,不会发生时空扭曲或祖母悖论。

现在我试一试用git checkout 时间点, git返回了如下信息: image 意思就是告知我,现在已经和现实分离,随便玩。“现实”就是HEAD,所以现实分离状态就是detached HEAD。不管怎么样都可以,add, rm, commit等等。 但是,如果做了些实验发现挺好的,想保留,那么就要新建立一个分支来保持这些变动。然后呢,再让这个分支去和主流合并,这之后就是正常merge流程了。

那么回到过去,修修改改后,想保存并建立分支需要用如下命令:

git checkout -b <new-branch-name>

实际上它是把两个单独的命令合到一起,一个是git branch <name>建立新分支并保存当前的改变,和另一个git checkout <name>跳转到该分支,这样一步到位还是挺方便的。

返回到现在进行时

当跳跃到过去到某个点时,它是绝对的detached Head状态。 在各种时间跳跃后,简单一句git checkout master就可以跳回到现在进行时了。当然,也可以是git checkout 某分支名跳跃到任何一个平行宇宙的现在进行时。

Git checkout 的妙用:撤销更改

git checkout 某文件名则可以让某个自己不满意的文件,回到最近一个时间点,即最近一个commit提交。

solomonxie commented 6 years ago

❖ Git的diff理解与学习 [DRAFT]

git diff和系统的diff命令是不同的,git diff是用作对比两个文件的差别,但是它是对这个文件和它在时光轴上的某个点上的自己做对比。当然git diff也可以用作--------

明白这点,就好理解多了。 先看这幅图: image

git diff可以用当前工作区的某文件,来进行:@1 它和自己保存在缓冲区的复制品对比,@2 它和过去每一个commit时光点上的自己对比。 当然,对比开始后,显示结果就和系统diff显示的大同小异了。

# 当前工作区与缓冲区的对比
git diff [指定对比的文件,或不指定也行]

#  缓冲区与过去commits对比
git diff --staged [指定对比的文件,或不指定也行]

本次commit与上次commit的diff

参考文章。 最简单写法:

git diff HEAD^ HEAD
# or
git diff @~..@
# or
git show
# or with GUI display
git difftool HEAD^ HEAD
solomonxie commented 6 years ago

Git的Reachability理解 [DRAFT]

根据现有commits和branches的树形结构,有很多节点是无法访问到的,

solomonxie commented 6 years ago

Git pull遇到错误 refusing to merge unrelated histories

git checkout master
git merge origin/master --allow-unrelated-histories
solomonxie commented 6 years ago

Git 在当前目录之外的地方执行命令

# Clone a repos
git clone <URL> <PATH>

# Run git command at another folder
git -C <PATH> <COMMAND>
solomonxie commented 6 years ago

Git 添加remote远端仓库对应

参考文档

git remote add origin https://github.com/user/repo.git

# Branch master set up to track remote branch master from origin.
git branch -u origin/master
solomonxie commented 6 years ago

Git push 遇到 failed to push some refs to 错误

一直在让脚本自动push本地仓库,仓库中的一些文件是从网上更新下来的,每次到push这一步都会产生错误,好像意思是remote有更新但是本地还没更新之前就push是不行的。但是所有remote更新都是本地push上去的,本地应该是一直保持在最新才对。。。不知道为什么

image

参考stackoverflow回答

在每次commit本地更新后,在push前用git pull --rebase origin master解决了。

solomonxie commented 6 years ago

如何让git记住密码

全局的话:

git config --global credential.helper cache

只是当前repo的话:

git config credential.helper cache

这两句话的做的工作是一样的,就是在~/.gitconfig全局配置文件或者.git/config当局配置文件中加入这段话:

[credential]
        helper = cache

所以直接找到配置文件加入这两句话也是一样的。

上面三种配置都完成后,git push的时候就会记住密码,下次不用再输入了。

不过有个问题是,最好在全局配置user.name 和user.email,不然的话还是需要每次都让你输入用户名密码。

solomonxie commented 6 years ago

❖ 如何用SSH与Github连接(push等)

为了让服务器自动push本地仓库,试过了git config credential.helper cache这样让它缓存用户名。 但是这个不稳定,每过一段时间github又会要求你再手动输入密码。 如果想让脚本自动推送,最可靠的还是用ssh访问github。 可是一般都知道,ssh要访问哪里,本地还是需要输入一个密码来解锁ssh的private key的,说白了就跟不用ssh连接直接输入密码没区别了嘛! 网上解决方案一般都是用什么openssl之类的,重新生成一个没有密码的ssh key。太麻烦, 发现其实ssh自带生成密钥的工具就能够设置不带密码的key。

方法: 直接输入ssh-keygen命令,就会提示输入key pair密钥对的地址和名称,不输的话就默认为~/.ssh/id_rsa这个文件作为私用密钥,同目录的id_rsa.pub作为公用密钥。 然后会提示输入密码,这时只要什么都不输直接回车,就可以创建好一个不用输passphrase的ssh key了。 然后到github的个人设置页里,找到ssh处,把id_rsa.pub的内容完全拷贝进去保存即可。 这时候,随意的git push之类与远程仓库连接都不需要输什么密码了。

问题: 目前只有默认名的~/.ssh/id_rsa这个管用,自己起名字的ssh key现在还不能用。

针对Github某个Repo指定SSH key进行连接

在个人帐户里,可以设置全局的SSH-key,任意操控各个repo。 其实,还可以限制权利,只针对某个repo设置ssh-key。

方法是:打开Repo -> Settings -> Deploy keys -> Add deploy key -> 把自己指定的ssh-key粘贴进来。 下次就可以免密码与github连接了。

但是,这里还有一个问题:如果针对每个repo设置不同的key,怎么让git push选择用哪一个key进行通讯呢? 看下面:

指定SSH key对Github进行连接

这个问题网上一样众说纷纭,因为没有官方设计的直接方法,git push也没有指定key的选项。

目前有这几种常用方式可以达到指定key的作用:

在当前用户目录下建立/修改~/.ssh/config文件,在里面指定默认的key文件位置。唯一的问题就是,只能指定一个key。

~/.ssh/config中的设置大概格式是:

Host github.com
    HostName github.com
    IdentityFile /path/to/your/personal/github/private/key
    User dandv

Host github-work
    HostName github.com
    IdentityFile /path/to/your/work/github/private/key
    User workuser

注意,ssh-key文件的权限需要是400,所以要记得修改权限:

$ chmod 400 ~/.ssh/id_rsa_github
solomonxie commented 6 years ago

Git与python的Virtualenv冲突

如果在git仓库中使用了virtualenv,那么每当使用git add .或者git commit都会遇到错误,使得不能继续。问题就在于virtualenv对它的冲突。 目前简单的解决办法就是在.gitignore中把所有virtualenv相关的文件都屏蔽,一般包括.Python,lib,include,bin

solomonxie commented 6 years ago

Git通过proxy代理访问

假设本地的代理端口为1087,那么命令行设置:

git config --global http.proxy http://127.0.0.1:1087

或者直接在~/.gitconfig中添加:

[http]
    proxy = http://127.0.0.1:1087
solomonxie commented 6 years ago

用SSH连接github项目时报错ssh_exchange_identification: Connection closed by remote host

一直在正常通过ssh连接github的repo,在哪里都没有改动的情况下突然git push不管用,一直报这个错误,就连git clone一个ssh地址都不行。 还以为github把我屏蔽。 考虑了网上很多本地ssh连接的问题,都没用,而且思路也不对。 索性ssh连接到我在新加坡租用的服务器,在服务器里尝试ssh连接github。 结果还是不行! 所以不是Github屏蔽了我的IP,也不是我本地的ssh设置问题,因为服务器的ip地址和ssh等都不一样还是遇到了这个问题。 网上还有说路由器的加速插件问题,这个也通过用服务器连接和切换wifi的方式否定掉了。 结果我心里只有一个结论:那就是Github本身的问题了。 结果没过几分钟, 所有端都可以正常连接了。 也许真的是github突然遇到什么问题导致所有地方都不能访问。

第二天更新: 结果真的第二天就收到这个新闻:Github昨天晚上遭遇史上最大DDOS攻击。但是不包括我吧,我顶多每分钟10次连接,而且还是用的绑定的ssh key和oauth码。。。 image

solomonxie commented 6 years ago

❖ Git 连接远程仓库

如果本地已经有了一个repo,想要与远程服务器如Github上的repo建立连接,然后push和pull等,就需要用git remote命令操作建立连接。

参考:阮一峰 Git远程操作详解 参考:Git Basics - Working with Remotes

# 检查现有远程连接,如果没有,则显示空
git remote -v

# 添加远程连接, URL可以是远程repo的http或ssh地址 (先设定为origin)
git remote add origin URL

# 或者修改现有远程URL (这里先设定为origin)
git remote set-url origin URL

# 先与远程同步
git pull origin master

# 提交到远程
git push origin master

# 将本地新建的分之推到远程(远程也新建相应的分之)
git push -u origin <branch>

这个时候还不能直接git push,因为没有设置默认push到远程的origin master分支,需要设置默认分支。

参考:Git push与pull的默认行为

git config push.default simple

然后就可以安心的git push直接推送到远程的origin master了。

更多参考: Github Help: Adding a remote Github Help: Changing a remote's URL

solomonxie commented 6 years ago

❖ Git branch 分支学习 [DRAFT]

新建分支

$ git checkout <new-branch-name>

# 以上一句话等同于以下两句话:
$ git branch <new-branch-name>
$ git checkout <new-branch-name>

不同分支的修改状况

分支间拷贝文件 Copy files between Git branches

  1. use the git show command:

    $ git show <branch_name>:path/to/file > /path/to/local/file
  2. use the git checkout command:

    
    $ git checkout <branch_name> path/to/new/file

如:将master分支的abc目录下所有文件克隆到本分支内

$ git checkout master:abc/ -- .


## 从远程克隆所有分支
默认`git clone`只会下载master分支,想要下载所有分支,网上众说纷纭,而且很复杂,连写bash脚本的都有。
总算找到一个简单且合理可用的:
```sh
# 删除原先的.git
mv /path/to/repo/.git /tmp/

# 下载包含所有branches的.git
git clone --mirror <URL> /path/to/repo/.git

# 进入目录
cd /path/to/repo/

# 重新初始化
git init

# 取消自动add的文件
git reset HEAD .
solomonxie commented 6 years ago

Git merge 合并分支

本地分支操作

# 先切换到主分支(或即将被覆盖的分支)
$ git checkout master

# 将某个指定分支覆盖到当前分支
$ git merge <branch-name>

# 删除之前的分支 (已经没用了)
$ git branch -d <branch-name>

远程分支操作

本地不管怎么push,如果不指定push的分支的话,远程是没有变化的。

# 把本地分支更新到远程分支
$ git push origin <branch-name>

# 删除远程分支:语法非常奇怪 但就是这么操作的
$ git push origin <local-branch-name>:<remote-branch-name>
# 如 $ git push origin :dev
solomonxie commented 5 years ago

❖ Git LFS (Large File Storage) 二进制文档版本控制

一直有对二进制文档版本控制的需求,比如一些修图的文档,图片库什么的。之前不懂,一直在用原生git进行控制,结果原本的2G文件夹,很快变成了4G+。 然后参考了git文档发现,官方是不推荐git进行二进制文档控制的。

然后顺着思路找,发现二进制版本控制是有的,而且是git系统能提供的:Git LFS

Git LFS需要单独下载,看似是独立于git的另一个程序,但其实只是相当于一个git插件的存在。

主要好处有:

问题:

参考Github官方:Git Large File Storage

安装和初始配置

# Mac
$ brew install git-lfs

初始配置(只需要在第一次安装时配置一次):

$ git lfs install

生成和配置本地项目

到这里,就要进入lfs领域了。需要说明的是, 其实绝大多数时间里,你都还是在使用原生的git命令。

我在一开始用时,脑袋非常混乱:到底怎么看待git lfs?另一套程序?还是git的一个指令? 其实都不是,如果非要说的话,把它看出git的一个extension插件,看成一个Filter过滤器更为方便。

现在来说一个典型的Git LFS Workflow流程:

实际上,Git LFS在这里的作用是一个Filter,把大文件过滤出来,不对它使用文本的处理方式增大体积,而采用另一套方案处理。 所以你只要一开始建立好filter,后面就不用再管了。

# 指定监视的文件类型
$ git lfs track "*.jpg"
$ git lfs track "*.png"
$ git lfs track "*.jpeg"

连接远程仓库

使用的Gitlab来做LFS仓库时,第一次push会出现以下消息:

Locking support detected on remote "origin". Consider enabling it with:
  $ git config lfs.https://gitlab.com/jason/test.git/info/lfs.locksverify true

然后照着它的提示,输入命令后再push,就没有问题了。

常用的Git LFS远程连接有几项常用方法:

$ git lfs clone <URL>

$ git lfs pull

$ git lfs push

# 断点续传(GB级别的仓库常用)
$ git lfs fetch

这几项至关重要,如果没有加lfs三个字的话,效率真的极低。 lfs的远程逻辑完全不同: 比如下载文件的话,不像git原生一个一个下载,lfs是先把所有文件夹、文件名都创建好,然后再把真实所需的文件下载下来。

注意事项

一定要git lfs clone, git lfs pushgit lfs pull

如果不是使用git lsf指令clone、push、pull的话,git就会按照正常的步骤把所有文件和所有版本全部下载下来,对二进制文档来说效率极低。 所以注意这里一定要指定lfs!

solomonxie commented 5 years ago

Git 乱码 不显示unicode字符问题

一直以来使用git,在出现中文时都是这种样子 ▼

image

习惯了,就没管。但是现在才觉得非常不方便,所以开始找解决方案。

一般来讲,修改这一句话就够了:

$ git config --global core.quotePath false

如果还不行,Mac上参考这个设置:

This option is only used by Mac OS implementation of Git. When core.precomposeUnicode=true, Git reverts the unicode decomposition of filenames done by Mac OS.

$ git config --global core.precomposeunicode true
solomonxie commented 5 years ago

Git push报错UNPROTECTED PRIVATE KEY FILE!

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/Jason/.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 "/Users/Jason/.ssh/id_rsa": bad permissions
Permission denied (publickey).
fatal: Could not read from remote repository.

其实问题是,我拷贝来拷贝去,不小心改变了~/.ssh文件夹的权限。所以再把权限改回来就好了:

$ chmod 0400 ~/.ssh/id_rsa 
solomonxie commented 5 years ago

❖ Git pull 从主仓库更新到fork仓库

当我fork了一个人的repo后发现,我不能简单的git pull来自于源仓库的更新,只是在我自己的repo中玩耍。 搜索了一下发现,需要先把源仓库的remote端加入进来,才能pull。

假设源仓库是git@github.com:yychuyu/LeetCode.git,而我fork后的仓库是git@github.com:solomonxie/LeetCode.git

# 添加源仓库的remote地址,相较于自己的远端名字"origin",给它任意起个名字:"source"
git remote add source git@github.com:yychuyu/LeetCode.git

# 指定pull自源仓库的master分支
git pull source master

# 下载更新后自动会然后进入commit提交页面
# commit更新
# 修改有conflict的文件,然后再次commit
# ...

# 将本地的更新push到自己的remote
git push origin master

记住,添加了源仓库的remote后,只能pull而不能push,因为没有权限。这时候需要到github网页上,提交pull request,然后等待源分支的主人允许合并。

pull方法其实是不推荐的,因为会自动覆盖当前分支,产生冲突。还有另一种fetch方法,把更新下载到本地后,我们可以为它创建一个分支,然后再选择性merge到自己的master分支即可。

git fetch source master

# 切换到刚刚更新的源分支
git checkout source/master
solomonxie commented 5 years ago

Git 更改文件夹名称

因为直接用mv更改文件夹名称,会被git识别为删除了旧文件创建了新文件,也就丢失了追踪,所以我们要用git mv命令。

如果是想改文件夹名称的大小写,那么就要麻烦一点:

$ git mv MyName temp && git mv temp myname
solomonxie commented 5 years ago

Git Branching [DRAFT]

Check out to a remote branch (not exists locally):

git checkout -b feature01 origin/feature01

Delete all local commits on this branch:

git reset --hard @{u}

Fetch ALL remote branches (you have to do it manually):

git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done

Fetch for current branch from ALL remote servers:

git fetch --all

Update all local EXISTING branches from remote:

git pull --all
solomonxie commented 5 years ago

How To Use Tig Like A Boss [DRAFT]

In the first place, I use tig as a CLI lightweight alternative for GitKraken or other heavy GUI Git clients. It indeed massively increases the speed of my git workflow at work. As an unexpected benefit, whenever I use tig in front of my co-workers, they are all like, "man, you are good!"

Now let's talk about how to learn this fancy tool in a minite.

Check Git Diffs Like A Boss

This is my favourite mode at tig, which is way much better than bare git diff command. It almost gives you a feeling that, "life is short, I use tig to diff".

To check what differences a commit made, simply choose a commit and press enter:

image

To expand the diffs to full screen, press capital O (press q to leave). To check next/previous diff of commits, you don't need to q and enter, but only Ctrl-n and Ctrl-p.

Add & Commit Changes

Press s to the status view, and interactively "move up" or "move down" by pressing u, which means to add file to stage or remove file from stage.

image

When you have choosed which files to commit, press capital C to commit (it will pop out the commit editor)

Show Git References

What are git refs? In a brief, a ref is just an alias for the commit-ID of a branch's latest commit.

In any mode of tig, you simply press r (reference) to see all the references:

image

Browse With Tig Like a File Manager

Press t (tree) in any mode:

image

solomonxie commented 5 years ago

What is a Git Reference?

So Basically, a git ref is just an "alias" of a commit hash key like d914f for each branch's newest commit (HEAD).

Actually, it's not only a branch's latest commit, but also some other type of special commits. The types of refs:

Refer to: Deep dive into git: Git refs

For example, I have a fix-decoding branch, and the hash key for its HEAD (newest commit) is d914f9ad7d6547167f2f182ba7daa7174431b7ed. What git does for our convenience is to create an alias for that as a file at .git/refs/heads/fix-decoding, which has only the content d914f9ad7d6547167f2f182ba7daa7174431b7ed.

git-refs

So what you have in the .git/ref directory is:

How to see all references?

$ git reflog
  ceb40ab HEAD@{0}: commit: commit empty
  2937af1 HEAD@{1}: commit: Commit empty file
  e3c3a05 HEAD@{2}: commit (initial): add file 1

HEAD: A Special Ref

Assume that you're at a fix-decoding branch, and the latest commit is f09096f. Now you want to do:

$ git show f09096f
commit f09096f8...........
...............

$ git show HEAD
commit f09096f8...........
...............

What you are looking at means that f09096f = HEAD. Exactly, the HEAD is just an alias for the Latest commit of CURRENT branch.

How about other branches? All the "heads" files are located in .git/ref/heads, which lets you and git know the HEAD for every branch. And when you perform a git checkout xx, git copy the specific head of current to be as .git/HEAD.

solomonxie commented 5 years ago

Git Submodule

Refer to: Using submodules in Git - Tutorial

.gitmodules

[submodule "binary"]
    path = binary
    url = git@git.github.com:Jason/binary.git
[submodule "locale"]
    path = locale
    url = git@git.github.com:Jason/locale.git
    branch = master

Clone project with submodules:

git clone --recursive [URL to Git repo]

Pull all submodules at once:

git submodule update --init
# if there are nested submodules:
git submodule update --init --recursive

Download up to 8 submodules at once:

git submodule update --init --recursive --jobs 8
git clone --recursive --jobs 8 [URL to Git repo]
# short version
git submodule update --init --recursive -j 8
solomonxie commented 5 years ago

Move the most recent commits (multiple) to another branch

The story is:

After a few hours of hard work & submitted a few commits, and realized that all the commits were submitted at the MASTER branch !!!

Something like this:

master A - B - C - D - E

in which C - D - E are the newest commits I made on master branch that should be on a feature branch. What I expected:

newbranch     C - D - E
             /
master A - B 

What should be done is to commit on feature-branch then merge to master, but definitely not directly commit on master branch.

What I'm gonna do to fix it is easy:

(master)$ git checkout -b new-feature
(new-feature)$ git checkout master
(master)$ git reset --hard head~3

The key command is git reset --hard head~3 which deletes the most recent 3 commits.

solomonxie commented 5 years ago

Git push error: [remote rejected] remote: error: cannot lock ref 'refs/heads/mybranch': Unable to create '/path/to/project/project.git/./refs/heads/mybranch.lock': File exists.

Refer to: https://stackoverflow.com/questions/6656619/git-and-nasty-error-cannot-lock-existing-info-refs-fatal

Solution:

$ git remote prune origin
$ git push origin mybranch
solomonxie commented 5 years ago

Git: How to View Single File Change History

$ git log -p /path/to/file
# or (same):
$ git log --follow -p /path/to/file

Set ~/.gitconfig:

    history = log --follow -p
$ git history /path/to/file

View with Tig

$ Tig /path/to/file
solomonxie commented 4 years ago

Git: How to force merge/overwrite from one branch to another

Scenario: The feature-branch has been merged to master-branch, then something bad happened, and you decid to roll-back. Then you want to fix the problem on feature-branch again...

Easier way

Refer to: https://superuser.com/questions/692794/how-can-i-get-all-the-files-from-one-git-branch-and-put-them-into-the-current-b

Scenario: You want to copy all files from feature

git checkout master
git checkout feature .

Harder way

Refer to: How do you force a merge with Git?

I don’t care if there is any other conflict. My branch A will win. Always:

git checkout A
git merge -s ours master
git checkout master
git merge A
solomonxie commented 4 years ago

Git: How to copy a file from another branch

Refer to: How do I copy a version of a single file from one git branch to another?

Change the file to another version:

git checkout other-branch myfile.txt

Copy a file from another file in another branch:

git show commit_id:path/to/file > path/to/file
solomonxie commented 3 years ago

Git: How to delete all branches except master?

Simple solution-1:

git checkout master
git branch -D $(git branch)

^ This will hard delete all branches except the current one (because its name got a *)

Simple solution-2:

cd ..
mv /path/to/project /tmp
git clone https://path/to/project

^ This will remove the whole project and download again :) be very careful unless you're sure of the risk