git的LF和CRLF问题

文本文件所使用的换行符,在不同的系统平台上是不一样的。UNIX/Linux 使用的是 0x0A(LF),早期的 Mac OS 使用的是 0x0D(CR),后来的 OS X 在更换内核后与 UNIX 保持一致了。但 DOS/Windows 一直使用 0x0D0A(CRLF) 作为换行符。

跨平台协作开发是常有的,不统一的换行符对跨平台的文件交换带来了麻烦。最大的问题是,在不同平台上,换行符发生改变时,Git 会认为整个文件被修改,这就造成我们没法 diff,不能正确反映本次的修改。

Git为了解决上面提出的问题,会自动对换行符进行转换。转换的方案有3种:

  1. 在提交时将CRLF转换为LF,在拉取(检出checkout)时将UNIX换行符(LF)替换成CRLF。(Windows系统推荐使用,我们在windows上安装git的时候,如果一路next,默认是使用这个方案)
  2. 在提交时将CRLF转换为LF,在拉取(检出checkout)时不进行转换。(Linux/Unix和Mac OS和Mac OS X推荐使用,在Unix或者类Unix操作系统上安装git,默认使用这种方案)
  3. 不进行转换(这种方案对于跨平台项目不推荐使用)。
# 提交时转换为LF,检出时转换为CRLF
git config --global core.autocrlf true

# 提交时转换为LF,检出时不转换
git config --global core.autocrlf input

# 提交检出均不转换
git config --global core.autocrlf false

一般在项目中,为了避免项目中同时出现CRLF和LF,还可以开启safecrlf检查。当然,如果你的项目自己定义了语法检查规则,例如使用eslint去约束换行符必须是LF,那么当你的文件中出现CRLF的时候,eslint会给你错误提示信息,告诉你不能包含CRLF,这时候,不开启safecrlf也是可以的 (一般建议开启)

配置方法:

# 拒绝提交包含混合换行符的文件 (一般设置为true)
git config --global core.safecrlf true   

# 允许提交包含混合换行符的文件
git config --global core.safecrlf false   

# 提交包含混合换行符的文件时给出警告
git config --global core.safecrlf warn

git status显示中文名称

git 默认中文文件名是 \xxx\xxx 等八进制形式,是因为对0x80以上的字符进行quote转义。 只需要设置core.quotepath设为false,中文文件名则会显示正常。

git config --global core.quotePath false

使用全局ignore

可以将 .gitignore 文件指定为适用于所有本地 Git 存储库的全局 ignore 文件。 为此,请使用 git config 命令,如下所示:

git config --global core.excludesfile <gitignore file path>

注意,单个仓库也有core.excludesfile参数,可以指定.gitignore的路径,加上–global后表示全局生效。但是要注意,单个仓库的config参数优先级要高于global。

将commit生成patch

git format-patch -3 //从当前分支最新提交点往下共生成3个补丁

git format-patch -1 指定commit号 //生成指定commit号的补丁 eg:git format-patch -1 5f123e3 生成5f123e3号的补丁,该commit号不一定在该分支开头,可以在该分支的任意位置。

打patch

git apply –reject your.patch 自动跳过有冲突项的合入,保留reject文件,手动解冲突

查看历史

git log 查看当前仓的历史提交 git reflog 查看包括已舍弃的所有提交 git log -p [-num] 查看历史提交的具体改动内容, num限制查看前多少个 git log –stat 查看改动的简要信息 git show 查看最近一次提交内容,相当于git log -p -1 git diff 显示文件修改后还没有暂存起来的内容

撤销

回退到指定版本 git reset –hard/soft <需要回退的版本> : 不保留暂存区和工作区/保留暂存区和工作区

恢复工作区 git checkout – <filename/.> : 单文件/全部 checkout用工作区或暂存区的状态还原这个文件 git checkout HEAD^ <filename> 从指定版本还原 git checkout <branch-name> – <filename> 从指定分支还原

丢弃掉工作区的改动 git restore –worktree <filename> 包括文件的新增删除

取消文件的暂存 git reset HEAD <filename> 把暂存区的修改撤销掉, 重新放回工作区, HEAD表示用最新的版本, git restore –staged <filename> 丢弃掉暂存区中的某文件,但工作区不受影响

修改最近的一次提交,重新提交 git commit –amend

撤销某次提交 git revert <某次提交>

删除

git中的删除也是修改 git rm <filename> && git commit -m “remove a file” git rm相当于删除工作目录中的文件, 并把此次操作提交到暂存区 所以需要commit将此操作提交到版本库中,进行最终确认 否则永远可以checkout恢复回来

分支

每个分支有一个分支指针, 指向分支起始的commit. 比如master分支的master指针, dev分支的dev指针. 在dev分支上的提交, 会使dev指针向前移动一步, 而master指针不受影响

切换分支只需要把HEAD指向对应的分支节点即可

创建并切换到新分支

    git checkout -b *分支名*
    git switch -c *分支名*
            用switch更科学 ### 删除分支
git branch -d *branchname* ### 重命名分支
git branch -m *old* *new* 解决merge冲突后, 再commit一次来完成这次merge

标签

tag,是一次标记。比如在当前分支上开发到了一个稳定版本,这时候就可以打一个标签,用于记录这个commit id。

不同于分支,分支就像一条高速公路,而标签是其路上的里程碑。

checkout用于切换分支,当checkout标签的时候,就会出现头指针分离的情况(detached)。这是代码会回到标签指向的commit,但是不能做任何修改,因为标签只是一个快照,头指针只是“暂时”回到这里以供查看。结束以后需要git pull来使头指针head回到HEAD。

协作

多人协作的工作模式通常是这样:

首先,可以试图用 git push origin branch-name 推送自己的修改;

如果推送失败,则因为远程分支比你的本地更新,需要先用 git pull 试图合并;

如果 git pull 提示 no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令 git branch --set-upstream-to branch-name origin/<branch-name>。

如果合并有冲突,则解决冲突,并在本地提交;

没有冲突或者解决掉冲突后,再用 git push origin <branch-name> 推送就能成功!

贮藏

存储本地工作区的快照备份到git栈内 git stash

合并stash内的工作区快照 git stash pop

合并失败则需要手动解决冲突, 再合并 git stash drop

命名 git stash save “ ”

什么叫分离头指针

Git在使用的时候有一种状态,叫做分离头指针状态,也叫detached HEAD

Git中HEAD指针指向分支,而分支是指向提交。所谓的分离头指针状态就是HEAD指针不再指向分支,而是直接指向某个commit。

正常状态结构图:

分离头指针状态结构图:

分离头指针状态的含义,其实本质上是说我们现在正工作在一个没有分支的状态下

  • 不好的方面:

在这分离头指针状态下,你可以继续产生commit,并且不会影响到其他的分支。如果你有需要切换到其他的分支需求,这个时候一旦切换,那分离头指针状态下开发出来的commit,因为没有branch和他挂钩,这些没有branch挂钩的commit,最后很可能被Git当作垃圾给清除掉,所以这个是他危险的地方。

也就是说如果你想变更工作分支,请先把这些提交和某个分支挂钩,此时这些commit,Git是永远不会把他清除掉的,这就是分离头指针使用的时候需要注意的地方。

  • 好的一方面:

就是我们想做一些变更,而这个变更你只是尝试性的变更,没准实践下来你觉得效果不好,你完全随时可以把他扔掉,扔掉就是你不要去理会他,你切换到新的分支就可以了,这就是分离头指针状态带来的好处。

远程分支

远程分支其实就是远程代码仓库当中的分支

当我们在使用git clone的时候,git会自动地将这个远程的repo命名为origin,拉取它所有的数据之后,创建一个指向它master的指针,命名为origin/master,之后会在本地创建一个指向同样位置的指针,命名为master,和远程的master作为区分。

也就是说,origin的含义指的是远程的仓库。它只是一个标记,就和默认分支叫做master一样,本身并没有特别的含义。如果我们愿意也可以起其他的名字,但是一般没有人这么干。

代码拉取

git pull并不是严格意义上的代码拉取命令,至少它还不是最细粒度,其实还有一个比git pull更加细粒度的操作。它就是——git fetch。

实际上git fetch才是真正的代码拉取的操作,它的作用是将远程的改动同步到本地。当我们执行git fetch origin的时候,这里的origin指的是远程的名字,如果你有多个远程的话要指定的话需要加上,否则可以不写。它会把远程所有的改动和分支都拉取到本地,命名为origin/xxx。origin的分支我们用git branch是看不到的,它只能看到本地的分支名,如果想要查看可以使用git branch -r。

当我们使用git checkout切换过去的时候,可以不必加上origin,我们需要git生成一个本地的分支指针,也指向同样的节点。git pull和git fetch的区别在于,这两者从表面上来看都是拉取远程的改动。但是两者针对的范围不同,git fetch针对远程的所有改动,而git pull只针对当前分支对应的远程分支。另外git pull执行之后会将远程的改动merge到本地的分支,也就是说它其实多了一步merge的操作。

代码推送

什么情况下git push就可以,什么情况下需要加上origin呢?

这里涉及一个机制就是本地的分支是不会自动和远程同步的,比如远程有人创建了一个test分支,我们拉取到本地会叫做origin/test。我们也可以自己创建一个test分支,和它井水不犯河水。这也是为了方便,如果直接用名称映射的话,可能会有潜在的冲突。并且由于可能会存在多个远程repo,所以我们push的时候也会有多种选择。

最完整的push命令是应该写成这样的:

git push origin test:cz/test

我们注意到这里用了一个奇怪的写法test:cz/test,它的意思是说将本地的test分支推送到远程作为cz/test分支。如果我们想要本地的名称和远程一样,我们可以省略简写成:git push origin test。

提交gerrit代码:git push origin HEAD:refs/for/分支名

如果我们设置过当前test分支的上游是远程的test,或者本地的test就是从origin拷贝过来的,那么我们可以直接git push,它会自动将本地的分支与远程关联上,会方便很多。实际上我们大多数的push操作都是这么进行的。将本地分支和远程建立映射可以使用这个命令:

git branch --set-upstream-to master origin/master

它表示的是将本地的master和远程的master进行关联,设置过关联之后我们只需要git push和git pull就可以更新和推送这个分支了,会方便很多。

rebase

变基,很直接的命名。因为rebase命令就是把当前分支指针所基于的commit变化到另一个commit。

git rebase master

例如在基于master拉出的feature分支上进行开发,与master产生了分叉。当feature需要回合到master的时候,我们常见的一种做法是git merge。而merge会从分支的地方再生成一个merge提交,会在原来commit上多出一个提交来。这样显然让人觉得不适。所以这个时候选择rebase就是很好的。

rebase会找到两个分支的最近的共同祖先,这就是最初的“基”,然后对比当前分支相对于这个基的分叉提交,提取相应的修改存为临时文件,再将当前分支指向目标基地master指向的最新提交,以此为新的“基”,将之前暂存的临时文件应用上去。这就是“变基”,把当前分支的基由原来分叉的地方,变更为了最新的地方。

rebase是本分支嫁到对面去。merge是把对面拉过来。

rebase就像是直接嫁接过去。而merge是两股绳捻到一起,并在系住的地方绑一个结。

相关笔记

  • git和repo的关系
  • 向社区提pr