Git 问题与修复
git worktree Git worktree 是 Git 提供的一种在同一个仓库中同时维护多个工作目录(Working Tree)的机制。它的核心目标是:让你在不同分支上并行工作,而不需要频繁切…
git worktree
Git worktree 是 Git 提供的一种**在同一个仓库中同时维护多个工作目录(Working Tree)**的机制。它的核心目标是:让你在不同分支上并行工作,而不需要频繁切换分支或重复克隆仓库。
它解决了什么问题?
- 频繁切换分支的痛点
传统方式:
1git checkout main
2# 改代码
3git checkout feature-x
4# 改代码
问题:
- 容易丢失未提交的修改
- 上下文频繁切换,效率低
- 多任务并行开发困难
例如:
- 一个分支修 Bug
- 另一个分支开发新功能
- 还要切回主分支测试
worktree 可以让每个任务拥有独立目录
一个分支只能绑定一个 worktree,同一分支,在同一时间,只能被一个 worktree 使用
git clone 免密登录 Gitee
使用 Git 的凭据帮助器(credential helper)来实现。
git config --global credential.helper store
执行一次需要用户名和密码认证的 Git 操作,比如git pull或git push。之后 Git 会提示您输入用户名和密码,并且会把这些凭据保存到本地。
Git 同时推送 Gitee 和 GitLab
方法一:为 origin 添加多个 push-url
1# 添加 Gitee 和 GitLab 地址
2git remote add origin https://gitee.com/zailiang/ai-vue.git
3
4# 为 origin 添加 Gitee push 地址
5git remote set-url --add --push origin https://github.com/zailiangs/ai-vue.git
6git remote set-url --add --push origin https://gitee.com/zailiang/ai-vue.git
7
8# 验证配置
9git remote -v
10git remote show origin
11# 输出应包含:
12# origin …(fetch) gitee
13# origin …(push) gitee
14# origin …(push) gitlab
15
16# 提交一次测试
17git add .
18git commit -m "测试 Gitee + GitLab 同步"
19git push origin main
方法二:新增一个聚合 remote(例如命名为 all)
1# 添加一个新 remote all 来聚合 Gitee 和 GitLab
2git remote add all http://192.168.2.41:8880/gw/nantong-competition.git
3
4# 为 all 添加第二个 push-url(GitLab)
5git remote set-url --add --push all https://gitee.com/zailiang/nantong-competition.git
6
7# 推送时使用 all
8git push all main
首次 set-url 是覆盖 push 源
1➜ ai-vue git:(main) ✗ git add .
2➜ ai-vue git:(main) ✗ git commit -m "first commit"
3➜ ai-vue git:(main) git remote -v
4➜ ai-vue git:(main) git remote add origin https://gitee.com/zailiang/ai-vue.git
5origin https://gitee.com/zailiang/ai-vue.git (fetch)
6origin https://gitee.com/zailiang/ai-vue.git (push)
7
8➜ ai-vue git:(main) git remote set-url --add --push origin https://github.com/zailiangs/ai-vue.git
9➜ ai-vue git:(main) git remote -v
10origin https://gitee.com/zailiang/ai-vue.git (fetch)
11origin https://github.com/zailiangs/ai-vue.git (push)
12
13➜ ai-vue git:(main) git remote set-url --add --push origin https://gitee.com/zailiang/ai-vue.git
14➜ ai-vue git:(main) git remote -v
15origin https://gitee.com/zailiang/ai-vue.git (fetch)
16origin https://github.com/zailiangs/ai-vue.git (push)
17origin https://gitee.com/zailiang/ai-vue.git (push)
- 由于你用了 –add 标志,它不会替换已存在的设置,而是把新的 push URL 追加 到已有列表中。
为什么第一次 –add –push 看起来像是“覆盖”了 push URL?
这是因为此前并 未 显式设置过 push URL,而是只依赖 fetch URL 作为默认 push 地址(两者相同)。因此第一次添加行为如下:
- 当前未存在显式 push URL,于是 Git 将 默认的 push URL(原先等同于 fetch URL)替换为你新指定的 URL。
- 结果列表中只剩下你新指定的 push URL,看起来像“覆盖”了之前的默认。
而在第二次执行相同命令时:
- 已存在一个显式 push URL,上次操作已经把它设为新的 GitHub 地址。因此这次再加 –add,就真正追加了另一个 push URL(即回到 Gitee),形成了两个
提交记录带有详细描述
1git commit -m "title" -m "detail1" -m "detail2"
或
1git commit
2# 进入编辑器 第一行是标题,提供了对变更的快速概述。后面的行是详细描述,提供了对变更的深入说明。
3修复登录页面的错误
4
51.修复了在用户输入无效密码时,页面没有反馈的问题。
62.更新了相关的验证逻辑,并添加了错误提示。
73.此更改提高了用户体验,使用户在登录时能及时获得反馈。
8# 保存
可用 git log查看提交记录
按功能或修改范围提交代码
分组更改:将相关的更改放在一起,每次只提交一组相关的更改。
逐个添加文件:使用 git add <file> 添加需要提交的特定文件,而不是使用 git add .
1# 修改了登录页面和用户注册页面
2git add src/views/Login.vue
3git commit -m "修复登录页面的样式" -m "在用户输入无效密码时,页面现在会显示错误提示。"
4
5git add src/views/Register.vue
6git commit -m "添加用户注册功能"
1# 或者每次只改动一处使用
2git commit -a -m "修改内容"
本地分支开发来合并到主分支
创建分支:每次开发新功能或修复bug时,先创建一个新的分支。这样可以将相关的提交集中在一起。
在分支上工作:在分支上进行修改后,再合并到主分支。
1# 创建新分支
2git checkout -b feature/login-page
3# 进行修改并提交
4git add src/views/Login.vue
5git commit -m "修复登录页面的样式"
6# 切换回主分支
7git checkout main
8# 合并新分支
9git merge feature/login-page
10# 推送
11git push origin main
新仓库推送代码命令
-u 是 --set-upstream 的简写。这个选项的主要作用是将本地分支与远程分支建立追踪关系
1cd existing_folder
2git init --initial-branch=main
3git remote add origin http://192.168.2.41:8880/gw/intelligence-ai.git
4git add .
5git commit -m "Initial commit"
6git push -u origin main / git push --set-upstream origin main
本地修改提交到指定标签
1git add .
2git commit -m "Your commit message"
3git tag -a <tag_name> -m "Your message here"
4git push origin <tag_name>
再次向已存在的标签提交代码
Git 标签是指向特定提交的引用,一旦创建后,它们通常不会更改。不过,您可以通过以下步骤向已有标签(如 v1.0.1)提交代码的方式,实际上是先提交代码,然后移动标签以指向最新的提交。
1git checkout v1.0.1 # 检出标签所在的提交
2git checkout -b update-v1.0.1 # 创建一个新分支
3# 进行修改并提交
4git add .
5git commit -m "Update for v1.0.1"
6# 移动标签
7git tag -d v1.0.1 # 删除旧标签
8git tag -a v1.0.1 -m "Updated v1.0.1" # 在最新提交上重新创建标签
9# 推送更改和标签到远程仓库
10git push origin update-v1.0.1 # 推送新分支
11git push origin --tags # 推送标签
12# 如果推送标签已经在远程存在,则加 -f 强制推送或删除远程标签后重新推送
13git push origin --tags -f
14git push origin -d v1.0.1 & git push origin v1.0.1
本地修改导致拉代码冲突
使用代码暂存 git stash
远程主分支重命名
- 在本地仓库中重命名 master 分支为 main:
git branch -m master main - 将重命名后的分支推送到远程仓库:
git push -u origin main - 删除远程仓库中的旧 master 分支:
git push origin --delete master - 更新远程仓库的默认分支: 仓库 - 设置 - 分支 - 默认分支 - 编辑 - 选择 main 作为默认分支
初始化默认分支名称
Git 2.28(2020年7月发布)及之后的版本:Git 允许用户配置默认的分支名称,官方推荐的默认分支名称是 main。
Git 2.28 之前的版本:默认的分支名称是 master。
查看默认分支名称(从 Git 2.28 开始提供的配置项): 你可以使用以下命令查看当前 Git 的全局配置,看看默认分支名称设置是什么:
1git config --global init.defaultBranch
更改默认分支名称为 main: 如果你想将默认的分支名称改为 main,可以运行以下命令:
1git config --global init.defaultBranch main
手动指定分支名称: 如果你希望在初始化时指定分支名称,可以在执行 git init 时通过参数指定分支名称:
1git init --initial-branch=main
安装 Git LFS
git lfs (Git Large File Storage) 是一个 Git 扩展,用于管理和版本控制大文件。
- 使用包管理器安装 Git LFS
1brew install git-lfs # MacOS
2apt install git-lfs # Ubuntu
- 安装后初始化 Git LFS
1git lfs install
- 验证安装
1git lfs --version
存储凭证的不同形式
git-credential-store将凭证以纯文本形式保存在本地文件中。这个文件默认位于用户主目录下的.git-credentials文件中。git-credential-cache将凭证存储在内存中,并在指定的时间内有效。默认缓存时间为 15 分钟,可以通过配置进行调整。由于凭证存储在内存中,重启计算机后会丢失。
1git config --global credential.helper cache
2git config --global credential.helper 'cache --timeout=3600'
git-credential-osxkeychain专为 macOS 设计的凭证存储工具。它将 Git 凭据存储在 macOS 的钥匙串中。
1brew install git-credential-osxkeychain
2git config --global credential.helper osxkeychain
git-credential-manager一个跨平台的凭证管理工具,适用于 Windows、macOS 和 Linux。
1brew tap git/git-credential-manager
2brew install --cask git-credential-manager-core
3git config --global credential.helper manager-core
拉取时合并分歧
1hint: You have divergent branches and need to specify how to reconcile them.
2hint: You can do so by running one of the following commands sometime before
3hint: your next pull:
4hint:
5hint: git config pull.rebase false # merge
6hint: git config pull.rebase true # rebase
7hint: git config pull.ff only # fast-forward only
8hint:
9hint: You can replace "git config" with "git config --global" to set a default
10hint: preference for all repositories. You can also pass --rebase, --no-rebase,
11hint: or --ff-only on the command line to override the configured default per
12hint: invocation.
在使用git pull时候,Git 发现了本地分支和远程分支之间存在分歧,所以需要你指定如何将这些分支合并在一起,Git 给你提供了 3 种选项,根据自己的需求选择其中一种方式,指定一个默认行为:
1git config pull.rebase false # 合并(merge)
2git config pull.rebase true # 重基(rebase)
3git config pull.ff only # 仅快进(fast-forward only)
还可以在每次git pull时临时指定使用哪种方式,通过添加参数来覆盖默认配置:
1git pull --rebase | --no-rebase | --ff-only # 临时使用 rebase merge fast-forward only
后面git pull命令会触发需要你输入合并的提交消息,其中第 1 行是标题,后面行都是详细描述内容
1Merge branch 'main' of https://gitee.com/zailiang/vue-project
2# Please enter a commit message to explain why this merge is necessary,
3# especially if it merges an updated upstream into a topic branch.
4# Lines starting with '#' will be ignored, and an empty message aborts
5# the commit.
本地分支落后远程分支
1 ! [rejected] main -> main (non-fast-forward)
2error: failed to push some refs to 'https://gitee.com/zailiang/vue-project.git'
3hint: Updates were rejected because the tip of your current branch is behind
4hint: its remote counterpart. Integrate the remote changes (e.g.
5hint: 'git pull ...') before pushing again.
6hint: See the 'Note about fast-forwards' in 'git push --help' for details.
1git pull
2git fetch & git merge
推送无上游分支
1fatal: The current branch main has no upstream branch.
2To push the current branch and set the remote as upstream, use
3
4 git push --set-upstream origin main
5
6To have this happen automatically for branches without a tracking
7upstream, see 'push.autoSetupRemote' in 'git help config'.
当前的 Git 分支 main 没有与远程仓库(origin)中的对应分支关联。简单来说,Git 不知道要将本地的 main 分支推送到远程仓库的哪个分支。
因此,在你尝试使用git push命令时,Git 提示你需要先设置一个 “上游分支”(upstream branch),即本地分支和远程分支之间的关联。
1git push --set-upstream origin main
2git branch --set-upstream-to=origin/main
Pull 与 Fetch 的区别
-
git pull实际上是git fetch和git merge的组合命令。它首先从远程仓库下载更新,然后将这些更新自动合并到当前分支。这意味着在执行git pull后,你的当前分支会立即包含远程分支的更改。 -
git fetch用于从远程仓库下载所有新的提交和数据,但不会自动合并这些更新到当前分支。它仅仅是将更新的数据下载到本地,使你可以查看和审查这些更改。
| 命令 | 作用 | 影响 |
|---|---|---|
git fetch |
下载远程更新,但不自动合并到当前分支 | 本地分支不变,可以审查更新 |
git pull |
下载远程更新并自动合并到当前分支 | 当前分支被更新 |
Merge 与 Rebase(变基) 的区别
merge是一种将两个分支的历史结合在一起的方式,通常用于将一个功能分支的更改合并到主分支(如main或develop)中。merge会保留两个分支的提交历史,并创建一个新的合并提交(merge commit)。
1A---B---C feature
2 \ /
3 D----E master
rebase是将一个分支的更改应用到另一个分支的顶部,通常用于在合并之前将功能分支的提交“平铺”到主分支上。rebase会重写提交历史,使得历史记录更线性。
1A---B---C feature
2 \
3 D----E master
| 特性 | merge |
rebase |
|---|---|---|
| 历史 | 保留所有提交历史,可能产生合并提交 | 重新排列提交,使历史线性,消除合并提交 |
| 适用场景 | 适合于需要保留分支历史的场景 | 适合于需要保持历史整洁、简洁的场景 |
| 处理冲突 | 在合并时解决冲突一次 | 在每个提交时可能需要解决冲突多次 |
- **使用 **
merge:- 当你希望保留完整的历史记录,尤其是在团队合作中,可以选择合并方式。
- 适合于大规模的项目,尤其是多个开发者同时在多个分支上工作的情况下。
- **使用 **
rebase:- 当你希望保持提交历史的整洁,尤其是在个人项目或较小团队中,可以使用变基方式。
- 在准备将功能分支合并到主分支之前,可以先进行变基,以确保没有多余的合并提交。
Switch 与 Checkout 的区别
在 Git 中,switch 和 checkout 都可以用于切换分支,但它们的用途和设计上有一些不同。switch 是 Git 2.23(2019年)引入的一个新命令,用来简化分支切换操作,并避免 checkout 的多功能性带来的困惑。
| 操作 | git checkout |
git switch |
|---|---|---|
| 切换分支 | git checkout <branch_name> |
git switch <branch_name> |
| 创建并切换到新分支 | git checkout -b <new_branch_name> |
git switch -c <new_branch_name> |
| 恢复文件到某个提交的状态 | git checkout <commit_hash> -- <file> |
不支持恢复文件 |
| 功能复杂性 | 多功能(切换分支、恢复文件等) | 专注于分支切换,功能更简单 |
尽管 git checkout 仍然可以用于分支切换和其他操作,但其多功能性有时会带来混淆。
git switch 和 git restore(另一个新命令,用于恢复文件)一起引入,旨在分离 git checkout 的功能,提供更清晰的用途:
**git switch**:用于切换分支。**git restore**:用于恢复工作目录中的文件或目录到某个提交的状态。
切换标签
如果您只是想查看标签的内容,git checkout v1.0.1 是一个简单直接的方法。
如果您希望在标签的基础上进行开发,使用 git switch -b <new_branch_name> v1.0.1 是更好的选择。这样可以避免因处于“分离头指针”状态而可能导致的提交丢失问题。
Reset 与 Revert 的区别
**git reset**:
- 直接移动分支指针到指定提交,并可选择清除暂存区和工作目录的更改。
- 例如:
git reset --hard HEAD~1会删除最后一次提交和所有相关的更改。
**git revert**:
- 生成一个新的提交,撤销指定提交的所有更改。
- 例如:
git revert <commit_hash>会创建一个新的提交,使之前的提交影响反向生效。
HEAD 引用移动
1git reset --hard HEAD~1
**HEAD**:表示当前分支的最新提交(也就是你当前所处的提交)。
**~1**:表示向后移动一个提交。因此,HEAD~1 指的是当前提交的上一个提交。
1* be4cdbf (HEAD -> main) 提交信息 A
2* 7fef373 提交信息 B
3* 1234567 提交信息 C
HEAD 指向 be4cdbf(最新的提交 A)
HEAD~1 指向 7fef373(提交 B)
HEAD~2 指向 1234567(提交 C)