Open solomonxie opened 6 years ago
最简单的Git工作流--即给初学者的工作流,尽量避免多分支,现在master分支上把常用指令学明白,然后再开启分支合流模式。
一般会提到git init
这个指令,在本地某个文件夹执行它就会把这个文件夹建立成一个git项目。但是我们初学者一般不是这个流程,我们需要建立一个github的repo,并将本地和它联通,反而简单很多。
首先直接到github首页新建一个repo,建好了以后直接点clone按钮复制.git结尾的链接。在本地用git clone
命令克隆到本地生成一个文件夹项目。如果本地已经做了一些文件,那就把文件复制进这个文件夹就好了。
命令如下:
$ git clone 项目克隆网址 本地路径
然后进入文件夹开始项目即可。
先不涉及远程repo仓库,git需要在本地完成提交,常规三步如下:
# 查看本地文件变动状态
$ git status
# 添加变动文件到预备区
$ git add --all
# 完成提交
$ git commit -m "变动描述"
然后本地的准备就完成了,随时可以连接远程仓库。
一般情况下,远程仓库都是我们自己的,拥有所有权限,所以暂不涉及向其他人的仓库提交(pull request)一类概念。
所以只需推送到远程自己的仓库,一句话git push
即可。
然后如果在安装git后设置过通用的用户名和邮箱,这里就只会要求你输入密码,然后就可以上传本地提交到远程repo仓库里了。
就这么简单。前三步基本流程如下图:
有的时候会用别的机器(比如公司)提交一些变化到远程,然后回家后想把变化同步到本地。
如果远程也是自己的repo拥有完全权限,那么直接git pull
即可完成一切同步。
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的配置内容,强烈建议不用命令输入,而是直接到这个文件里面改。参考下图:
这个是默认状态下的配置文件:
这个是利用git config
命令后的文件:
所以为了日后方便管理,还是直接改文件的好。 另外,详细初始配置,如默认编辑器是vim还是emacs,differ用哪个编辑器显示等细节,参考git官方文档。起步 - 初次运行 Git 前的配置
整个项目里面肯定会有这个文件的,用来屏蔽一些文件的记录和上传。比如一些临时文件夹和涉及隐私的文件,就不需要传到github上了。
简单说起来就是:
!
就是不被屏蔽的,用于屏蔽整个文件夹时却不屏蔽其中的某个文件;#
是注释符号;*
会代替名字里不限量的字,如*.gif
, test*.html
;/
,就会屏蔽其中所有文件和文件夹,包括子文件夹引用一个网上文章的例子:
# 忽略 .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教程都只会解释,当文件是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
清除掉cache后,git会显示你已删除了这些文件(但实际上本地还存在),然后提交变动。这样git就再也感知不到它的存在了。
在Mac上,可能之前重装vim变动了一些设置,所以才会有这个错误,导致git不能提交。
查了后解决方案很简单,直接输入:
git config --global core.editor $(which vim)
之前讲过在电脑本地文件夹中~/.gitconfig
文件即可设置本机的通用用户名,用来登录远程。
但我常会把公开代码上传到github,私密代码上传到Bitbucket,所以需要不同的登录名等。
Git其实可以为每个repo设置单独的用户名用来登录远端,像global的用法一样,可以命令行里设置也可以直接在文件里写:
$ git config user.name "John Doe"
$ git config user.email johndoe@example.com
手动改写文件的话,就位于repo目录中的.git/config
这个文件。
设置的格式和~/.gitconfig
完全一样,具体参考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>
最近在考虑有的时候想托管一些国内方便的内容比如当cdn使用,还有码云什么的,比较了下易用性和容量,仅有的几个托管商里也就阿里云最合适,也最接近github。 链接 其它都是正常操作,唯一遇到的问题是git push的时候总是无法完成用户认真,在git里面设置了user.name和email等都不行。 原来阿里云code的登录密码是单独设置的,不能直接用自己的账号密码去登录, 必须要在阿里云Code的管理后台里面找到密码设置,然后选择忘记密码(修改密码没用,因为没有原始密码,只能点忘记密码)。
发邮件确认后,设置密码,就可以登录了。 所以这里git push时,用户名是阿里云的账号email, 密码是Code的单独密码。 参考官方文档。
但是!这时候还不能做push等远程操作。如图:
repo页面里面,一不小心漏过了一条提示:
按照提示,在Profile->sshkeys页面里面,把本地电脑里~/.ssh/id_rsa.pub
这个公钥文件内容粘贴到里面,添加密钥:
阿里云还需要设置每个参与人员的权限。在控制台里可以找到,如下图:
但是不管我怎么尝试,都添加不了members,项目中都members总是为0。
最后结果还是不能push。
官方解释的非常清楚,在这里,包括各种访问方式、大文件处理方式、repo存储空间提示等等的情况。 说白了,Github几乎没有任何容量限制,但是你不怀好意地把github当网盘用,频繁访问、不断传大文档等,是会收到通知的。
有很多时候,git追踪某一个文件的变化历史和轨迹,是非常重要的,不管是代码还是文档。这里主要说文档。但是我们的笔记、文档,总是喜欢改名和转移目录之类行踪不定,那么一旦有这些改动,git还能追踪吗?
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却认为你是删了一个而新建了另一个。
这是因为和改名一样,只要是在git命令之外移动的,git就识别不到。
所以,这里也要用git mv
命令来移动。
git mv 1.txt ./src/1.txt
这个时候在查看git状态就会发现,显示为renamed: 1.txt -> src/1.txt
这个问题逻辑要复杂点:有时候我们需要批量抓取网上的一些资源到这个目录里面,有些是重复的内容一样的,有些却是新的,还有些是网上删除了的,那么我们想要网上抓取的和本地的同步,应当怎么办呢?
虽然这个方法不严谨,但是非常简单有效:只要这些东西体积不是很大,那么就可以完全删除本地现有的文件,然后再把新抓取的保存到本地。 这个时候就是考验git的追踪识别能力了。 经过一系列试验发现:
git rm
删除文件,如果用了它那么不管怎么做,它都会知道你删除了文件,而我们要做的是让它误以为没有删除。git status
也不会显示任何变动。git status
只会告诉你modified 某文件
。git mv
命令告知git,但是这个逻辑比较麻烦,尤其是涉及文件比较多的时候。所以我一般选择避免改名字,或干脆就断了追踪。在git里面,有一个叫
index
的区域,你把东西加到那里叫add
, 把东西再从哪里撤回来叫reset
;已经在里面的我们形容它是staged
,还没有加进去的我们形容它是unstaged
。
其实index区
就是一个纯粹的缓冲区,也叫staging area
,是正式提交之前给我们的一个缓冲,还有犹豫的余地。因为一旦正式commit提交了,你所有还没解决的愚蠢的傻事都会公开,即使能覆盖能撤销,但还是掩藏不了历史。
自己做的话无所谓其实,但是如果是团队合作的话,每次commit都是一次公开。
其实形容的话,就相当于老板让你做个项目,你肯定不可能做了一点东西就跑到老板办公室去送一趟文件,应该会先把做好的放在桌子的上那个小文件架上。然后那个文件架就叫index
。
# 指定文件
$ git reset HEAD file.txt
# 全部撤销
$ git reset HEAD .
# 指定文件
$ git checkout -- file.txt
# 全部撤销
$ git checkout -- .
一旦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
一般来说,
merge
是新手的噩梦 。所以为了还有学习的动力,我前期几乎放弃,只是一心只用最简单的功能,等像现在这样慢慢理解了基本东西了,了解基本知识局限性了遇到很多问题了,才是好机会来加强理解这一层。
当我们谈到merge的时候,实际上是有很多种不同的情况的。比如有这几种情况的merge,分别是:local merge
,sync merge
和fork merge
。(这都是我自己起的名字)
Merge本身并不难理解,无非是从这边融合到那边,不同的只是起点终点的方向罢了。
Merge的难点在于merge conflict
,就是融合的时候有冲突怎么办?
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
中看到。
可以肯定的是,不会发生时空扭曲或祖母悖论。
现在我试一试用git checkout 时间点
, git返回了如下信息:
意思就是告知我,现在已经和
现实分离
,随便玩。“现实”就是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 某文件名
则可以让某个自己不满意的文件,回到最近一个时间点,即最近一个commit提交。
git diff
和系统的diff
命令是不同的,git diff
是用作对比两个文件的差别,但是它是对这个文件和它在时光轴上的某个点上的自己做对比。当然git diff
也可以用作--------
明白这点,就好理解多了。
先看这幅图:
git diff
可以用当前工作区的某文件,来进行:@1 它和自己保存在缓冲区的复制品对比,@2 它和过去每一个commit时光点上的自己对比。
当然,对比开始后,显示结果就和系统diff
显示的大同小异了。
# 当前工作区与缓冲区的对比
git diff [指定对比的文件,或不指定也行]
# 缓冲区与过去commits对比
git diff --staged [指定对比的文件,或不指定也行]
参考文章。 最简单写法:
git diff HEAD^ HEAD
# or
git diff @~..@
# or
git show
# or with GUI display
git difftool HEAD^ HEAD
根据现有commits和branches的树形结构,有很多节点是无法访问到的,
pull
遇到错误 refusing to merge unrelated histories
git checkout master
git merge origin/master --allow-unrelated-histories
# Clone a repos
git clone <URL> <PATH>
# Run git command at another folder
git -C <PATH> <COMMAND>
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
failed to push some refs to
错误一直在让脚本自动push本地仓库,仓库中的一些文件是从网上更新下来的,每次到push这一步都会产生错误,好像意思是remote有更新但是本地还没更新之前就push是不行的。但是所有remote更新都是本地push上去的,本地应该是一直保持在最新才对。。。不知道为什么
在每次commit本地更新后,在push前用git pull --rebase origin master
解决了。
全局的话:
git config --global credential.helper cache
只是当前repo的话:
git config credential.helper cache
这两句话的做的工作是一样的,就是在~/.gitconfig
全局配置文件或者.git/config
当局配置文件中加入这段话:
[credential]
helper = cache
所以直接找到配置文件加入这两句话也是一样的。
上面三种配置都完成后,git push的时候就会记住密码,下次不用再输入了。
不过有个问题是,最好在全局配置user.name 和user.email,不然的话还是需要每次都让你输入用户名密码。
为了让服务器自动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现在还不能用。
在个人帐户里,可以设置全局的SSH-key,任意操控各个repo。 其实,还可以限制权利,只针对某个repo设置ssh-key。
方法是:打开Repo -> Settings -> Deploy keys -> Add deploy key -> 把自己指定的ssh-key粘贴进来。 下次就可以免密码与github连接了。
但是,这里还有一个问题:如果针对每个repo设置不同的key,怎么让git push
选择用哪一个key进行通讯呢?
看下面:
这个问题网上一样众说纷纭,因为没有官方设计的直接方法,git push
也没有指定key的选项。
目前有这几种常用方式可以达到指定key的作用:
ssh-agent
命令:不推荐GIT_SSH
环境变量:不推荐core.sshCommand
Git配置GIT_SSH_COMMAND
环境变量~/.ssh/config
配置文件在当前用户目录下建立/修改~/.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
如果在git仓库中使用了virtualenv,那么每当使用git add .
或者git commit
都会遇到错误,使得不能继续。问题就在于virtualenv对它的冲突。
目前简单的解决办法就是在.gitignore
中把所有virtualenv相关的文件都屏蔽,一般包括.Python
,lib
,include
,bin
。
假设本地的代理端口为1087,那么命令行设置:
git config --global http.proxy http://127.0.0.1:1087
或者直接在~/.gitconfig
中添加:
[http]
proxy = http://127.0.0.1:1087
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码。。。
如果本地已经有了一个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 config push.default simple
然后就可以安心的git push
直接推送到远程的origin master
了。
更多参考: Github Help: Adding a remote Github Help: Changing a remote's URL
$ git checkout <new-branch-name>
# 以上一句话等同于以下两句话:
$ git branch <new-branch-name>
$ git checkout <new-branch-name>
add
和commit
,那么即使另一个分支,文件也一样是改变了的。use the git show command:
$ git show <branch_name>:path/to/file > /path/to/local/file
use the git checkout command:
$ git checkout <branch_name> path/to/new/file
$ 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 .
# 先切换到主分支(或即将被覆盖的分支)
$ 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
一直有对二进制文档版本控制的需求,比如一些修图的文档,图片库什么的。之前不懂,一直在用原生git进行控制,结果原本的2G文件夹,很快变成了4G+。 然后参考了git文档发现,官方是不推荐git进行二进制文档控制的。
然后顺着思路找,发现二进制版本控制是有的,而且是git系统能提供的:Git LFS
。
Git LFS需要单独下载,看似是独立于git的另一个程序,但其实只是相当于一个git插件的存在。
主要好处有:
问题:
.git/lfs/objects
文件夹中(以前是存在.git/objects
中)。参考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 init
初始化为git仓库git lfs track
指定监控的LFS大文件类型git add . && git commit
正常添加、提交仓库变化git lfs push
通过lfs优化推送到远程git add . & git commit -m "update"
正常git流程git add . & git commit -m "update"
正常git流程git add . & git commit -m "update"
正常git流程git lfs pull
通过lfs优化更新本地仓库git lfs push
实际上,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 push
和 git lfs pull
如果不是使用git lsf指令clone、push、pull的话,git就会按照正常的步骤把所有文件和所有版本全部下载下来,对二进制文档来说效率极低。
所以注意这里一定要指定lfs
!
一直以来使用git,在出现中文时都是这种样子 ▼
习惯了,就没管。但是现在才觉得非常不方便,所以开始找解决方案。
一般来讲,修改这一句话就够了:
$ 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
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ 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
当我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
因为直接用mv
更改文件夹名称,会被git识别为删除了旧文件创建了新文件,也就丢失了追踪,所以我们要用git mv
命令。
如果是想改文件夹名称的大小写,那么就要麻烦一点:
$ git mv MyName temp && git mv temp myname
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
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.
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
:
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
.
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
.
When you have choosed which files to commit, press capital C
to commit (it will pop out the commit editor)
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:
Press t
(tree) in any mode:
So Basically, a
git ref
is just an "alias" of a commit hash key liked914f
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
:
.git/refs/heads
.git/refs/remote/
.git/refs/tags
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
.
So what you have in the .git/ref
directory is:
heads
: The HEAD
file of every branch, each file only contains the commit-ID
.remotes
: All heads of all branches of all remtoe serverstags
: All tags, in which each tag contains the ID of specific commit.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
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
.
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
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.
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
$ 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
$ Tig /path/to/file
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...
Scenario: You want to copy all files from feature
git checkout master
git checkout feature .
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
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
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
Online Courses