Skip to content

Git - 更新操作

Tom 执行 git clone 来获取项目,该项目现在包含 Jerry 的 string_utils.c 文件(来自 git_perform_changes.json 场景)。他想查看历史记录,因此执行 git log。

[tom@devbox ~]$ git clone https://git.example.com/our_team/project.git
Cloning into 'project'...
...
[tom@devbox ~]$ cd project/
[tom@devbox project]$ git log

git log 的输出显示了 Jerry 的提交:

commit abc1234b140dad24b2c35b15cc7e26a6f02d2277 (HEAD -> main, origin/main, origin/HEAD)
Author: Jerry Mouse <jerry@example.com>
Date: Mon Jan 15 10:00:00 2024 +0000
Implement my_strlen function and main demo
commit 19ae20683fc460db7d127cf201a1429523b0e319
Author: Tom Cat <tom@example.com>
Date: Sun Jan 14 09:30:00 2024 +0000
Initial commit

Tom 查看 string_utils.c 并决定改进 my_strlen 函数,明确确保输入指针 s 在函数内部不被修改,尽管 const char *s 已经表明了这一意图。他还注意到原始教程提到了将返回类型更改为 size_t 并使用 const char *s,这是一个很好的实践。假设 Jerry 的初始版本更简单,而 Tom 现在使其更健壮。

Tom 修改 string_utils.c(例如:如果 my_strlen 还没有那样做,则使用 const char *s_ptr = s;,或者添加注释)。假设他为了说明添加了一个小改动:

// 在 string_utils.c 文件中,Tom 添加了一行注释:
// 一个计算字符串长度的自定义函数
// 通过使用 'p' 确保原始指针未被修改
size_t my_strlen(const char *s)
{
const char *p = s;
while (*p) {
++p;
}
return (p - s);
}

他使用 git diff 检查差异:

[tom@devbox project]$ git diff

输出将显示添加的注释:

diff --git a/string_utils.c b/string_utils.c
index xxxxxxx..yyyyyyy 100644
--- a/string_utils.c
+++ b/string_utils.c
@@ -1,5 +1,6 @@
#include <stdio.h>
// 一个计算字符串长度的自定义函数
+// 通过使用 'p' 确保原始指针未被修改
size_t my_strlen(const char *s)
{
const char *p = s;

测试后,他提交了他的更改:

[tom@devbox project]$ git status -s
M string_utils.c
[tom@devbox project]$ git add string_utils.c
[tom@devbox project]$ git commit -m "为 my_strlen 添加解释性注释"
[main def5678] 为 my_strlen 添加解释性注释
1 file changed, 1 insertion(+)
[tom@devbox project]$ git log -1
commit def5678e12fff493ba99508c5959e3789fa00000 (HEAD -> main)
Author: Tom Cat <tom@example.com>
Date: Mon Jan 15 11:00:00 2024 +0000
为 my_strlen 添加解释性注释

Tom 将他的更改推送到远程仓库:

[tom@devbox project]$ git push origin main

命令输出将显示推送成功:

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
...
To https://git.example.com/our_team/project.git
abc1234..def5678 main -> main

与此同时,Jerry(在他自己的本地克隆上工作,尚不知道 Tom 最近的推送)决定实现一个字符串复制函数 my_strcpy。他修改了 string_utils.c:

// Jerry 添加到 string_utils.c:
char* my_strcpy(char *dest, const char *src)
{
char *orig_dest = dest;
while ((*dest++ = *src++))
;
return orig_dest;
}
// 并更新 main 函数进行测试:
// ... (之前的 main 代码)
char buffer[50];
my_strcpy(buffer, "Hello Git!");
printf("Copied string: %s\n", buffer);
// ... (main 函数的其余部分)

测试后,Jerry 在本地提交了他的更改:

[jerry@devbox project]$ git status -s
M string_utils.c
[jerry@devbox project]$ git add string_utils.c
[jerry@devbox project]$ git commit -m "实现 my_strcpy 函数并添加测试"
[main ghi9012] 实现 my_strcpy 函数并添加测试
1 file changed, 10 insertions(+), 2 deletions(-) (示例修改)

Jerry 现在想推送他的更改:

[jerry@devbox project]$ git push origin main

推送操作失败:

To https://git.example.com/our_team/project.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://git.example.com/our_team/project.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git 拒绝了 Jerry 的推送,因为远程 main 分支包含 Jerry 本地没有的新提交(来自 Tom)。直接推送会覆盖 Tom 的更改。Git 通过要求 fast-forward 合并来阻止这种情况发生,而这里无法进行 fast-forward 合并。

Jerry 需要更新他的本地仓库。他使用 git pull。默认情况下,git pull 会拉取更改然后尝试合并它们。一个常见的替代方法是 git pull --rebase,它会将本地提交重新应用到拉取的更改之上,通常会产生更清晰的历史记录。

让我们展示 git pull --rebase 策略:

[jerry@devbox project]$ git pull --rebase origin main

输出可能如下所示(假设没有冲突):

From https://git.example.com/our_team/project
* branch main -> FETCH_HEAD
Successfully rebased and updated refs/heads/main.

如果 Tom 和 Jerry 对 string_utils.c 的更改之间存在冲突(例如,如果他们修改了同一行),Git 将暂停变基并要求 Jerry 解决冲突。假设在这个示例中没有冲突,Jerry 的提交 ghi9012 现在被重新应用在 Tom 的提交 def5678 之后。

Jerry 查看日志。他的本地 main 现在包含了 Tom 的提交,然后是他的(变基后的)提交:

[jerry@devbox project]$ git log --oneline --graph
* jkl3456 (HEAD -> main) 实现 my_strcpy 函数并添加测试
* def5678 (origin/main, origin/HEAD) 为 my_strlen 添加解释性注释
* abc1234 实现 my_strlen 函数及 main 演示
* 19ae206 初始提交

(注意:jkl3456 是 Jerry 变基后的更改的新提交哈希。)

现在 Jerry 的本地 main 已经是最新的,并包含了远程更改。他可以安全地推送了:

[jerry@devbox project]$ git push origin main

这次,推送应该会成功:

Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
...
To https://git.example.com/our_team/project.git
def5678..jkl3456 main -> main

这种工作流程(拉取/变基,解决冲突(如果有),然后推送)是处理并发更改的常用方法。