梦想家罗西的博客

但守陋巷,陈说平生

img

这篇文章已经在我脑海中酝酿了大约5周,可能会成为一个“活帖”,我会不断更新。

这是我在过去12年里,关于Git提交和提交历史的一些经验总结,顺序不分先后。这些经验来自于在公司中与2-12人团队的合作,以及在开源代码库中与各种贡献者的合作。

  1. Git有不同的用途——协作工具、备份工具、文档工具
  2. Git提交信息非常棒
  3. 我从未遇到过像我这样喜欢阅读提交信息的人
  4. 通过提交记录查找为什么做出某个更改,比通过问题/错误跟踪器更容易
  5. 一个包含“各种修复。DEV-123”的提交比一个仅包含“各种修复”的提交更好
  6. 一个包含“各种修复。DEV-123”但问题本身没有有用信息的提交更糟糕
  7. 我更喜欢rebase合并,然后是squash合并,再然后是merge
  8. 如果你不学习如何使用rebase,你会错过一个很好的技能
  9. 那些在事情出错时说“直接删除仓库”的人真的让我很沮丧
  10. 学习如何使用git reflog,它会帮你避免删除可恢复的仓库和工作
  11. 学习如何使用git reflog,你会发现可以从一些并不严重的错误中拯救自己
  12. 再多的花哨工具和命令的学习也无法避免偶尔犯错
  13. 我最近一次搞砸的rebase是在上周,我需要git reflog来帮我解决
  14. 学习如何撤销强制推送,然后学习如何更安全地强制推送(记住=ref!!)
  15. Squashing是对精心编写的原子提交的浪费
  16. Squashing比100个糟糕的提交要好
  17. 在合并时Squashing并写一个好的提交信息是好的
  18. Squashing后不重新编辑信息是最糟糕的
  19. Squashing时,如果有100个糟糕的提交而不重新编辑信息简直是犯罪
  20. Squashing后不重新编辑信息比从一个分支的100个糟糕提交中合并提交还要糟糕
  21. 写好PR/MR描述但不使用它来指导压缩合并信息是浪费时间
  22. 写提交信息帮助我发现了缺失的测试用例、缺失的文档或无效的思路,因为它帮助我重写了更改的原因
  23. 使用你的git log作为站会更新的依据是合理的
  24. 我懒得签署我的提交(除非被迫)
  25. 如果必须签署提交,SSH密钥签名让这件事几乎不那么糟糕
  26. 如果你在不同的仓库之间移动文件,你需要使用git subtree保持历史记录完整
  27. 提交应该是原子的——所有代码、测试和配置更改都应该在里面
  28. 我花了不少时间确保每个提交在CI检查中都是原子的
  29. 有些人做的事很糟糕,比如将实现代码和测试代码分开提交
  30. 将文档放在单独的提交中是可以的——我们不必在一个提交中交付整个端到端功能
  31. 使用压缩合并的仓库很糟糕
  32. 作为开源项目的维护者,我喜欢压缩合并,这样我可以重写贡献者的提交信息
  33. 有时教别人如何写提交信息并不值得
  34. 你周围的人会影响你写提交信息的方式
  35. 在前期做工作,使你的历史记录是原子的
  36. 事后将一个大提交拆分成原子提交更痛苦
  37. 原子提交对提高你的成就感有好处——你可以完成更多事情
  38. 原子提交非常适合预先重构
  39. 有时预重构提交可以进入单独的PR(特别是如果使用压缩合并)
  40. 写提交信息可能比实现花费更长时间
  41. 提交信息的长度可能是提交更改行数的十倍
  42. 如果你在提交信息中写了很多“和”或“也”,你可能在尝试做太多事情
  43. 过去通过查阅Git提交历史帮助我理解了很多没有原作者解答的原因
  44. 提交信息是反思你所做的事和为什么要做的一个很好的节点
  45. 为什么比什么更重要——任何人都可以通过diff大致看出做了什么更改,但背后的意图是特别的调料
  46. 如果你只写了什么更改,我会讨厌你
  47. 解释了什么的提交比只写了“修复”的提交要好
  48. Chris Beams的文章《如何写Git提交信息》 仍然是一篇优秀的文章,即使在近10年后,仍然是一个很好的起点!
  49. 提交是提交者的假设和世界状态的时间点解释。不要对他们太苛刻
  50. 我不想看你的AI/LLM重写的更改——要么自己写,要么叫“各种修复”
  51. 需要有一种方法可以添加注释(可能使用git notes)到以前的消息中以纠正假设
  52. 我不会一开始就写完美的提交信息——它们有时会像“重写!添加SBOM支持”或“sq”那样简短,或者使用git commit –fixup
  53. 通常,我会把非常好的原子工作块分成提交
  54. 我有时会把原子提交拆分成多个提交
  55. 确保你在将代码更改发送给合作者审查之前自己先审查一遍
  56. 审查提交信息应该和审查代码更改一样重要
  57. 让所有贡献者对提交历史记录投入同样的关注是不可能的
  58. 尝试监管提交历史会很痛苦
  59. 试图在代码审查时强制审查提交信息会很痛苦
  60. 尝试监管提交历史会导致代码库中更高水平的文档和对更改的考虑
  61. 将隐含的假设变成显式假设非常有用
  62. 引入commitlint可能有用,但也很令人沮丧
  63. 让你的合作者愿意写好的提交信息比你强迫他们要好
  64. 有些人不写,而这没关系
  65. 写作是一种技能
  66. 我在写作(提交信息)方面并不完美
  67. 有时我懒得写完美的信息
  68. 有时我会写一些非常棒的提交信息,并自我陶醉
  69. 使用模板写Git提交信息是一个很好的提醒,帮助你正确完成
  70. fixup提交和git rebase –autosquash是我学到的最好的Git技巧之一
  71. 我重视在一个拥有多样化视角、技能和工作方式的团队中工作
  72. 但我也非常重视拥有一个写原子提交并有很好提交信息的团队
  73. 写提交信息与写精心提炼的用户故事/任务一样有用
  74. git commit -m sq可能是我运行次数最多的命令
  75. 使用git add -pgit commit -p对原子提交非常重要
  76. 永远不要使用git add -ugit add .
  77. 学会在何时可以使用git add -ugit add .
  78. 我真的需要研究像Graphite、git-branchless等工具,以提供一个堆叠的PR设置
  79. 使用conventional commits与semantic-release或go-semantic-release可以在想要自动化和频繁发布时产生巨大差异
  80. 使用conventional commits作为提交框架非常有用
  81. 对于有ADHD的人,使用conventional commits减少了思考的需要,有时可以让你更多地关注更改内容
  82. 使用conventional commits可以帮助你发现你是否在尝试在一个提交中做太多事情
  83. 我通过写作进行思考,因此提交信息帮助我理解为什么我做了这件事
  84. 写一个好的提交信息比写存储在其他地方的文档更好
  85. 写一个好的提交信息比写代码注释更好
  86. 给人们学习的空间
  87. 给人们失败的空间
  88. 记住你曾经也不那么出色
  89. 文档很棒,多做一些

来自:[1]:[https://www.jvt.me/posts/2024/07/12/things-know-commits/]

新蛋网作为一家外企,面试流程相对比较专业,面试会有邮件发面试邀请,邮件里面会写面试流程以及时间,面试结果也会在三个工作日之内通过邮件通知,最终面试没有通过,但是整体感受不错,在此做一个复盘和记录。

笔试题

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
function a() {
console.log("function a");
}
debugger; //1
function b() {
console.log("function b");
debugger; // 2
function c() {
console.log("function c");
}
c();
}
debugger; //3
var d = function () {
console.log("function d");
};
b();
</script>
  1. function a和function d的定义方式有何不同?请列举你知道的所有区别,函数还有那些定义方式?
    • function a是函数声明的方式定义,function b是函数表达式
    • 函数声明的函数存在声明提升,也就是会被提升到作用域的最顶部

      function funDeclaration 存在函数提升所以不会报错而 function funExpression不存在函数提升所以报错
      1
      2
      3
      4
      5
      6
      7
      8
      funDeclaration("Declaration"); //=> true
      function funDeclaration(type) {
      return type === "Declaration";
      }
      funExpression("Expression"); //=>error
      var funExpression = function (type) {
      return type === "Expression";
      };
    • var声明的变量会有作用域提升,但是var函数表达式不会提升
      1
      2
      3
      4
      5
      6
      console.log(q); //=>undefined
      var q = "test";
      f(); //=>error
      var f = function () {
      console.log("function f");
      };
    • 还可以使用new Function()构造函数定义函数,如下:
      1
      var test = new Function('x', 'y', 'return (x+y)');
  2. 各注释(1-3)的debugger处,a,b,c,d的初始化情况,是否可以被调用?为什么
    1. debugger 1处
    • a可以被调用,b可以被调用(因为函数提升),c不可调用,d 也不可调用
    1. debugger 2处
      • a 可以被调用,b 可以被调用(注意可能会陷入死循环),c可以被调用,d不可以被调用
    2. debugger 3处
      • a可以被调用,b可以被调用,c不可调用,d 也不可调用
  3. c的作用域是?b每次执行的时候,c是否都会被初始化?为什么,可以如何证明?
    • c 的作用域如下:
    1. 局部作用域(Local Scope):c 函数被定义在 b 函数内部,因此 c 的作用域是 b 函数的局部作用域。c 可以访问 b 函数内部定义的所有变量和其他函数。
    2. 词法作用域(Lexical Scope):JavaScript 使用词法作用域,意味着函数的作用域在函数定义的时候就已经确定。因为 c 定义在 b 内部,所以它可以访问 b 内部的所有变量和函数,即使在 b 调用 c 时,它们之间的变量作用范围已经确定。

    见如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function b() {
    let localVar = 'I am a local variable';

    function c() {
    console.log('function c');
    console.log(localVar); // 访问 b 函数中的局部变量
    }
    c();
    }
    b();
    // Output:
    // function c
    // I am a local variable
    • b 每次执行的时候,函数 c 都会被初始化。原因如下:
      1. 作用域链和闭包机制:在 JavaScript 中,每次调用一个函数时,都会创建一个新的执行上下文和作用域链。函数 c 是在函数 b 内部定义的,因此每次调用 b 时,都会创建一个新的函数 c,即使它们的代码完全相同。这个新创建的 c 会被放置在新的执行上下文中,并且它的作用域链也会不同。

      2. 函数声明和变量提升:虽然函数声明在作用域内被提升,但在每次函数调用时,函数声明都会被重新初始化。这意味着函数 c 每次都会被重新声明和初始化。

      证明如下:

      我们可以通过在 c 函数内增加一个计数器变量来证明每次调用 b 时,c 函数都会被重新初始化。通过检查计数器变量的值,能直观地看到 c 函数是否是新的实例。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      let counter = 0;

      function b() {
      function c() {
      counter++;
      console.log('function c, counter:', counter);
      }
      c();
      }

      b(); // Output: function c, counter: 1
      b(); // Output: function c, counter: 2
      b(); // Output: function c, counter: 3


      在上述代码中,每次调用 b,c 都会增加 counter 的值,并且打印当前的 counter 值。由于每次调用 b 时 counter 的值都在递增,说明每次 b 调用时,c 都是一个新的实例。

对象的定义:”无序属性的结合,其属性可以包含基本值,对象或者函数”

创建一个自定义对象最简单的方式

创建一个object实例

1
2
3
4
5
6
7
let person = new Object();
person.name = "LUO FANGGUO";
person.name = 29;
person.job = "frontent engineer";
person.sayName = function(){
alert(this.name);
}

也可以使用对象字面量来创建自定义对象

1
2
3
4
5
6
7
8
let person = {
name: "",
age: 29
job: "frontent engineer"
sayName: function(){
alert(this.name);
}
}

对象的属性类型

要修改属性的默认的特性,必须使用ES5提供的Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象,属性的名字和一个描述符对象。

  1. 数据属性
    • [[Configurable]]:表示能否通过delete删除属性而从新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为true
    • [[Enumerable]]:表示能否通过for-in循环返回属性。默认值为true
    • [[Writable]]:表示能否修改属性的值。默认值为true
    • [[Value]]:包含这个属性的数据值。默认值为undefined

      值得注意的是:可以多次调用Object.defineProperty()方法修改同一属性,但是把Configurable设置为false之后就会有限制了。
  2. 访问器属性

    访问器属性不能直接定义,必须使用Object.defineProperty()来定义
    • [[Configurable]]:表示能否通过delete删除属性而从新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。这个属性默认值为true
    • [[Enumerable]]:表示能否通过for-in循环返回属性。默认值为true
    • [[Get]]:在读取属性时调用的函数。默认值为undefined
    • [[Set]]:在写入属性时调用的函数。默认值为undefined

定义多个属性

在ES5可以使用Object.defineProperties()方法来定义多个属性

读取属性的特性

使用ES的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在对象和要读取其描述符的属性名称。

最近在使用hexo搭建博客的时候,准备托管在cloudflare上面,本地部署没问题(本地环境为:macOS 14.4.1,node:20.5.0,npm: 8.6.0 ),push 到GitHub上并在cloudflare上配置Pages并连接git,配置如下:

图片

多次检查配置都没问题,运行环境也和本地一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
00:22:33.626	Using v2 root directory strategy
00:22:33.650 Success: Finished cloning repository files
00:22:35.475 Checking for configuration in a wrangler.toml configuration file (BETA)
00:22:35.476
00:22:35.616 No wrangler.toml file found. Continuing.
00:22:35.790 Detected the following tools from environment: [email protected], [email protected], [email protected]
00:22:35.791 Installing nodejs 20.5.0
00:22:36.732 Trying to update node-build... ok
00:22:36.941 Downloading node-v20.5.0-linux-x64.tar.gz...
00:22:36.941 -> https://nodejs.org/dist/v20.5.0/node-v20.5.0-linux-x64.tar.gz
00:22:38.560 Installing node-v20.5.0-linux-x64...
00:22:38.958 Installed node-v20.5.0-linux-x64 to /opt/buildhome/.asdf/installs/nodejs/20.5.0
00:22:38.958
00:22:40.405 Preparing [email protected] for immediate activation...
00:22:40.853 Installing project dependencies: yarn

到构建这一步报错了,好家伙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
00:22:41.433	➤ YN0070: Migrating from Yarn 1; automatically enabling the compatibility node-modules linker 👍
00:22:41.433
00:22:41.462 ➤ YN0000: ┌ Resolution step
00:22:42.085 ➤ YN0061: │ domexception@npm:4.0.0 is deprecated: Use your platform's native DOMException instead
00:22:42.093 ➤ YN0061: │ abab@npm:2.0.6 is deprecated: Use your platform's native atob() and btoa() methods instead
00:22:42.213 ➤ YN0061: │ cuid@npm:2.1.8 is deprecated: Cuid and other k-sortable and non-cryptographic ids (Ulid, ObjectId, KSUID, all UUIDs) are all insecure. Use @paralleldrive/cuid2 instead.
00:22:42.423 ➤ YN0032: │ fsevents@npm:2.3.3: Implicit dependencies on node-gyp are discouraged
00:22:42.838 ➤ YN0000: └ Completed in 1s 376ms
00:22:42.858 ➤ YN0000: ┌ Post-resolution validation
00:22:42.858 ➤ YN0028: │ The lockfile would have been modified by this install, which is explicitly forbidden.
00:22:42.858 ➤ YN0000: └ Completed
00:22:42.858 ➤ YN0000: Failed with errors in 1s 397ms
00:22:42.890 Error: Exit with error code: 1
00:22:42.890 at ChildProcess.<anonymous> (/snapshot/dist/run-build.js)
00:22:42.891 at Object.onceWrapper (node:events:652:26)
00:22:42.891 at ChildProcess.emit (node:events:537:28)
00:22:42.891 at ChildProcess._handle.onexit (node:internal/child_process:291:12)
00:22:42.902 Failed: build command exited with code: 1
00:22:43.827 Failed: error occurred while running build command

最后反复试了很多次,找到原因了,问题在这一行

1
00:22:33.626	Using v2 root directory strategy

只需要把配置生产构建系统的版本从2选到1就可以,默认是选择的2 具体构建系统版本1和2区别请点链接:👉language-support-and-tools

图片

在部署一次就发现部署成功了

图片

每个人在社会都会有自己的一些想法,人生经验积累,学习到的知识,日常琐碎事情或者精彩瞬间等等,把这些分享出来可以给自己带来好运气,博客是系统无疑是分享的媒介之一了,现在知名的博客系统种类繁多,我通过使用的技术栈,优势和缺点等整理如下:

框架名称技术栈优势缺点
Hexo Node.js
  • 生成速度快
  • 一键部署
  • 扩展性强
  • 丰富的主题
  • 静态网站没有后台
  • 不方便管理
  • 插入图片比较困难
  • 评论等功能需借助第三方工具
vuePress/vitePress vue.js+webpack/vue.js+vite
  • 界面简洁优雅
  • 高性能,灵活
  • 更好的兼容、扩展Markdown语法,可以在markdown中写vue组件
  • 响应式布局,PC端、手机端
  • Google Analytics集成
  • 支持 PWA
  • 不支持PDF导出文章
  • 不支持全文搜索,需要用其他插件
Hugo Go
  • 速度快
  • 静态页面,不需要数据库
  • 内置较多模板变量,专为博客而生
  • 支持多种语言,可以生成多语言网站
  • 难以实现CSS框架样式的按需引入
  • 评论等功能需借助第三方工具
Zola Rust
  • 生成速度快
  • 使用简单, zola 命令只有两三个参数
  • 自带了数十个语法高亮主题, 常用的 monokia 也在其中.
  • tera 模板简洁够用, 能完全满足目前的需求.
  • rss 功能不太完整.
  • 不是开箱即用的, 需要花些功夫配置一下主题等.
  • 源代码中自带的模板示例并不完整, 没有展示出 zola 的全部功能.
Jekyll Ruby
  • 有很多优秀的主题
  • 对于普通用户,上手困难
  • 生成文章时间太长
WordPress PHP
  • 简单暴力
  • 几乎所有功能的插件
  • 响应式网页
  • 慢、慢、慢
  • 小插件容易被钻空子,导致数据丢失
  • 插件容易起冲突
Solo Java 功能齐全,多端适配 主题少
Pelican Python
  • 部署简单
  • 体量轻盈
  • 加载速度很快
  • 方便使用git进行版本控制
  • 方便直接使用Markdown进行写作
  • 灵活性和扩展性相对较差
  • 功能通常较为简单
  • 图片的插入与管理较为繁琐(无媒体管理和富文本编辑器)
Octopress Ruby
  • 对内嵌代码支持很好。内置了 pygments ,这里有一份支持语言的列表。值得一提的是 octopress 还支持内嵌 Gist。
  • 日志文件都在本地,而且是纯文本,管理很方便(可以用 git),也不用担心租用的服务器数据丢失等问题
  • octopress 支持用 SCSS 自定义主题
文章多了编译难免很慢
gitbook node.js
  • 支持 Markdown 和 AsciiDoc 两种语法格式
  • 丰富的主题模块和插件模块
  • 文章阅读体验好
打包非常非常慢
Docsify Node.js 基于vue实现,非常轻量,不生成额外.html。可定制Markdown解析规则,支持流程图、LaTeX公式等 由于是完全运行时驱动,对检索优化(SEO)不太友好。插件的丰富程度相对差一些。
0%