摘要:在做 freeCodeCamp 中文社区 志愿者的时候,有幸参与在线课程的翻译工作,该博文是柳星大佬整理的翻译注意事项,只作搬运以备不时之需。
翻译格式与注意事项
请前往翻译格式与建议
关于 git 和 github
常用词汇
- repo:代码仓库
- PR:即 pull request,合并请求
- branch:分支
- commit:提交记录
- merge:指 PR 合并到代码仓库的操作
- conflicts:(合并)冲突
开始之前
首先,fork 一下 challenges repo。
把你的 fork 克隆到本地。
1
git clone https://github.com/your_name/challenges.git # 注意,your_name 是你的 github ID。
切换到 challenges 文件夹。
1
cd challenges
如果你没有关联过上游,请先:
1
git remote add upstream https://github.com/FreeCodeCampChina/challenges.git
根据你在翻译的项目名称或者你正在做的事情,新建分支。
1
2
3
4# 建议每次都基于 upstream 的 translate 分支新建你的分支
# 请参考后面的“常见问题”
git fetch upstream
git checkout -b your_branch_name upstream/translate # 其中 your_branch_name 是你的分支名在本地进行代码或文件修改。
添加要追踪的文件到暂存区。
1
2git add . # 注:这个命令不是永远都会添加你的所有改动,请参考“常见问题”。
git add my.json # 或者你也可以添加某一个文件提交 commit 到本地仓库。
1
2git commit -m "My commit message" # 注意,请根据实际情况填写 commit message。
git commit # 或者你也可以打开你喜欢的编辑器(需要配置),在里面编写 commit message。把本地仓库推送到远程仓库。
1
git push origin your_branch_name
打开 github 页面,创建 PR。
同步远程更新至本地
关联上游 repo 至本地项目。
1
git remote add upstream https://github.com/FreeCodeCampChina/challenges.git
获取上游更新,并应用到本地。
1
git pull --rebase upstream translate
注意
pull
或rebase
之后,如果有 conflicts,可以先使用git status
查看存在 conflicts 的文件。修改成需要的版本后,使用
git add .
然后git rebase --continue
。请注意,在这个过程中:不要 commit!不要 commit!不要 commit!
解决冲突之后,需要更新至远程,否则只有你的本地有更新。
1
git push origin your_branch_name
如果出现错误提示,请先使用
git status
命令检查本地是否有未解决完成的 conflicts。任何时候出现错误,不必惊慌。
先使用
git status
命令检查当前所在的分支、当前目录是否纯净(clean),以及本地是否有未解决完成的 conflicts。如果上一步没问题,你可以用
git push -f origin your_branch_name
来更新远程。如果你已经用当前的 branch 开了 PR,那么更新这个 branch 至远程的同时,你的 PR 也会自动更新。
常见问题
为什么 "git add ." 命令有时会添加不上我的改动?
注意,git add .
中的 .
表示“当前路径”。
因此,如果你通过 cd
命令切换到子目录,并在里面执行 git add .
,那么外面的改动则不会添加。
然而,如果你在父级目录执行 git add .
,子级目录里的文件改动是会添加的。
真正的“添加所有文件”的命令是 git add --all
,可以简写为 git add -A
。
对于这个翻译项目,我们很少会需要 cd
进子目录。因此,一般情况下使用 git add .
就足够了。
如何解决冲突?
对于任何多人协作项目,有 merge conflicts 是十分正常的。
如果你在命令行中看到了 CONFLICTS
这样的输出,那就表示有冲突。
这时,你需要先使用 git status
命令来查看冲突发生的文件。
一般来说,有冲突的文件会显示成这样:
1 | some code ....(这里的代码是没有冲突的) |
注意,里面的 HEAD
和 your_branch_name
位置可能互换,也可能会是其他内容,比如一个 commit hash。
其中,<<<<<<<
与 =======
之间为代码的一个版本,=======
与 >>>>>>>
之间为代码的另一个版本。
你需要来决定使用哪个版本的代码,修改的时候,把 <<<<<<<
、=======
和 >>>>>>>
这三行都删掉。
以及,删掉你不需要的那个版本,保留你需要的版本。
处理完所有的冲突文件后,(由于我们执行的是 git pull --rebase
),那么我们需要 git add .
,然后 git rebase --continue
。
如果某个文件我没有改动,在处理冲突的时候如何可以使用 upstream 上 translate 分支的版本?
有时,可能会存在你没修改某个文件的内容,然而它却出现在了 conflicts 里(特别是如果你之前使用过 pull
,而不是 pull --rebase
)。
这时,我们输入:。
1 | git fetch upstream |
这时,你本地的这个文件就变成和远程一样了。
处理之后,记得 git add .
。
如何查看我当前处于哪个分支?
git branch
可以列出本地所有的分支名,前面打星号(*)的就是你当前所在的分支。
如何切换分支?
git checkout some_branch_name
就可以切换到对应的分支。
以及,git checkout -
可以切换到上一个切换过的分支。
在两个分支之间来回切换的时候,这个命令会很有用。
新建分支的时候,与我当前所在的分支有关联么?
有,新建分支的时候,当前所在分支的所有 commit
也会添加到新的分支里面。
以及,如果你本地有未 commit
的改动(哪怕已经 add
过),同样会在新建分支的时候带过去。
既然切换 branch 时代码会跟着走,我正在别的分支上翻译,突然让我去更新之前开了 PR 的另一个分支,我该怎么办?
你有两个选择,commit
或者 stash
:
commit
很简单,在当前分支上git add .
然后git commit -m "xx"
,这时候你就可以使用checkout
命令切换到其他分支了。在当前分支上
git stash
,然后切换到其他分支。完成那边的更新后,切换回来,然后git stash pop
,你之前的代码改动就都回来了。需要注意的是,使用
git stash pop
会有丢代码的潜在风险,推荐使用git stash apply stash@{x}
,其中x
为一个数字。如果你不确定你的做法是否正确,或者不了解这个命令,请在使用之前查清资料,或者在群里提问。
切换分支前,为防止把本地弄乱,前先使用
git status
来检查本地是否 “clean”。*
我可不可以根据远程的分支(比如 upstream 的 translate 分支)来创建本地分支?
可以:
1
2git fetch upstream
git checkout -b my_branch_name upstream/translate
每次都从远程创建分支太麻烦,我可不可以直接从本地创建分支?
可以。建议使用本地的 translate 分支保持与 upstream 中的 translate 分支保持更新。这样做的好处是:
每次新建分支的时候,切换到本地的 translate 分支,然后
git checkout -b my_new_branch
就好了。如果 upstream 的 translate branch 有更新,你只需要在切换到 translate 分支之后,
git pull --rebase upstream translate
即可完成对本地 translate 分支的更新。再创建新的分支,就是基于 upstream 里最新的代码了,这样可以减少 conflicts 出现的可能。
我在一个分支上 commit 了我的代码,这时候 upstream 更新了,我该怎么做?
1 | git pull --rebase upstream translate |
我的本地 translate 分支已经有我的 commit 了,那我该如何用这个分支作为与 upstream translate 同步的分支呢?
如果你目前在 translate 提交的内容不再需要了(比如,已经 merge),那你可以先切换到 translate,然后:
1 | git fetch upstream |
虽然 git reset
命令不危险,但在执行这个操作之前,建议你先在群里问一下。
命令好长,我不想记。
alias
了解一下。在命令行里执行:
1 | git config --global alias.gx 'pull --rebase upstream translate' |
下次,执行 git gx
(记忆:git 更新),就会执行你定义好的命令了。
我已经开了 PR,但代码历史记录很乱,而且文件改动包含了不是我改的东西,如何修复?(多见于曾经在这个分支上用过 pull 命令,现在使用 pull --rebase 的情况,见下文分析)
如果对 git 不是很熟悉(特别是 git brease -i
以及 rebase
命令的原理),重建一个新的分支,然后把当前这个分支里属于你的 file change 给 apply 过去,再用新的分支开 PR 是最省事的做法。
假设你目前处于 translate-old
分支上,你改动了文件 02-javascript-algorithms/abc.json
以及 02-javascript-algorithms/abc.md
,且你已经用当前的 translate-old
分支开了 PR:
1 | # 获取 upstream 的 HEAD 指针 |
一些原则
建议使用 git workflow 来进行分支的管理。
这样我们可以提交 PR 之后继续在新的 branch 上进行后续的翻译,若需要更新当前的 PR,我们随时可以切换回来。
不建议同时开两个相同类型(比如翻译)的 PR,请等待之前的 merge 之后再开新的 PR。
如果你的 PR 已经被 review 过,请不要再添加新的翻译内容,根据 comment 修改好当前的 PR 即可。
后续的翻译你可以等当前翻译 merge 后再开始做,或者在另一个本地的 branch 进行后续的翻译。
你的 PR 中不应包含你未改动过的文件,请在提交的时候仔细检查。如果包含了,请参考上面的解决方案。大部分情况下,坚持使用
git pull --rebase
,不混用git pull
可以在很大程度上避免这个问题的产生。注:包含与否一般不会影响代码库,或导致功能缺失。但这会给 review、后续的版本控制和管理造成很大的麻烦:
- Review 的时候,需要仔细比对那些本不是你改的文件,确保你没有(在 resolve conflicts 或 commit 的时候)更改任何内容。
- 如果你在 PR 中引入了已经 merge 的 commits,那么就会在对应的 PR 中添加对你的 PR 的 reference。事实上,由于你本无意改动那些文件,这就只会对维护者和后续的开发者造成误导。
- 维护者本可以直接通过文件的最新 commit 找到对应的 PR,但由于其他人也包含了这个文件,则需要一步一步排查,看究竟是哪一步出了问题。
如果你想知道产生这个问题的原因,请参考以下的图形化解释:
关于 PR 中,引入他人更改文件的情况分析:
假设现在的
upstream/translate
是A -> B
这两个 commits,其中B
较新。你基于这个创建了你的my-translate
分支,那么你也会得到A -> B
。之后,你开始进行翻译,并
commit
了代码。现在你的my-translate
是A -> B -> X
,其中X
为你的 commit。然后你发现远程更新了,现在远程是
A -> B -> C -> D
。这次你使用了git pull
命令。那么在my-translate
分支,你就得到:A -> B -> X -> M
。其中,M
就是传说中的 merge commit,它包含了上游的C
和D
。(但从常理判断,这时候如果你得到
A -> B -> C -> D -> X
这样的历史线就更好了。这正是git pull --rebase
会帮你完成的事情,以及这也是我们推荐使用这个命令的原因。)然后你继续翻译,并
commit
了代码,现在你的my-translate
分支就是A -> B -> X -> M -> Y
,其中Y
是你的最新 commit。这时候你执行了
git pull --rebase
,那么问题来了。基于rebase
命令的比较原理(或者说算法),它会首先寻找一个你的my-translate
分支与upstream/translate
分支共同的”祖先 commit“(ancestor commit)。”共同的祖先 commit“(common ancestor commit)是指这两个分支开始出现分歧(diverted)之前的那个 commit。在这个例子中,它就会找到 commitB
,因为在B
之后,my-translate
分支是 commitX
,而upstream/translate
是 commitC
。rebase
的执行逻辑可以简化为git reset --hard
+git cherry-pick
(好奇的朋友可以去了解下这两个命令),那么cherry-pick X
的时候不会出问题(基于B
,添加你的翻译,显然不会有报错),但cherry-pick C
的时候就很可能会出现 conflicts:假设
C
中,其他人更新了README.md
(当然,在你的 commitX
和Y
中,你都没有修改这个README.md
),常理上来说,这件事应该发生在X
之前。但由于在你的分支my-translate
中,X
是先于M
(包含C
和D
)发生的,那么这里就造成了 Git 的困扰:它觉得,根据upstream/translate
分支,说好了B
之后就改README.md
的;然而在你的分支里,却告诉我B
之后README.md
不需要改,那我该怎么办?——那我就只能告诉你我遇到了 conflicts,请你手动解决下吧。这时,如果你错误地使用了
git commit
命令,那么 Git 就会觉得,这个README.md
你也改动过了,事实上你并没有,以及你也没打算改,这是我认为导致引入他人更改文件的一种原因。后续哪怕再
git pull --rebase
,Git 也会去找 commitB
作为 common ancestor。这依然会导致 conflicts,因为在my-translate
里,从一开始,B
之后是X
这件事就是错的。我认为的另一种可能,在这个例子中,就是如果后续还有其他人改了
README.md
那么本地执行git pull
的时候也会产生 conflicts,这时出现的根源是git merge
,因为目前my-translate
的HEAD
显然不是那个README.md
更改的 ancestor,因此 Git 没法 fast forward 那个新的README.md
改动,感兴趣的朋友可以去了解下什么是fast-forward
。此时需要手动处理 conflicts,处理之后git commit
,那么 Git 就会认为你也参与到了这个 commit 中。那么,
git pull --rebase
对这种情况会有什么帮助?还是上面的例子,你的
my-translate
是A -> B -> X
,远程以及是A -> B -> C -> D
了,其中C
里面更改了README.md
。如果你采用
git pull --rebase
,那么你的本地就会是A -> B -> C -> D -> X
。后续你又commit
了新的代码,比如现在你的本地是A -> B -> C -> D -> X -> Y -> Z
。此时,远程那边也加了几个 commit,变成了A -> B -> C -> D -> E -> F
。如果你继续
git pull --rebase
,那么 Git 此时寻找到的 common ancestor commit 是D
,而不再是上面使用git pull
的B
了。除非E
和F
里也改了你正在改的文件,否则就不太可能产生冲突。结果,你就会得到
A -> B -> C -> D -> E -> F -> X -> Y -> Z
,这也正是我们期望的结果。所以,麻烦大家花一点时间处理下。感谢 :pray: