版本控制

版本控制工具

主流版本:

  • Git
  • SVN(subversion)
  • CVS
  • VSS
  • TFS

版本控制分类

  • 本地版本控制

  • 集中版本控制:SVN

  • 版本都保存在服务器上。用户需要联网从中央服务器上拉下来最新版本再修改提交。必须联网才能工作,对带宽要求比较高。

  • 用户自己电脑上只有某一部分文件。

  • 分布式版本控制:Git

  • 每个用户电脑上都有完整的所有的版本数据。不会因为服务器坏掉造成不能工作的情况。

  • 协同方法:一个人改了文件A,另一个人也改了文件A,互相把修改推送给对方就可以看到对方的修改。

  • git是本地代码库,局域网联网就可以进行多人开发,把最终版本提价到远程即可。

Git基本理论

基本组成

图片

  • 工作目录 working directory

平时本地写代码的地方

  • 暂存区 stage / index

临时存放改动,只是一些文件。

  • 仓库区:本地仓库repository

  • 远程仓库:remote

托管代码的服务器

工作流程

  • 从远程仓库中把项目clone到本地

  • 在本地工作区中增删改

  • 将更改的文件add到暂存区域
  • 将暂存区域的文件commit到本地git仓库
  • 从本地仓库push到远程仓库

图片

git文件状态

文件四种状态

  • Untracked:
  • 未跟踪, 此文件在文件夹中, 但并没有add到本地仓库, 不参与版本控制。通过git add 状态变为Staged.

  • Unmodified

  • 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处, 如果它被修改, 而变为Modified。如果使用git rm移出版本库, 则成为Untracked文件

  • Modified

  • 文件已修改, 仅仅是修改, 并没有进行其他的操作。 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改 !

  • Staged

  • 暂存状态,文件已经暂存到暂存区。执行git commit则将修改同步到本地仓库中, 这时本地仓库中的文件和工作区文件变为一致, 文件为Unmodified状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified

查看文件状态

git status [filename]
git status

Git配置

  • 查看所有git 配置:
git config -l
  • 查看git系统配置:
git config --system --list
git config --system -e #直接打开配置文件
  • 查看git本地配置:
git config --global --list
git config --global -e #直接打开配置文件
  • 下载git bash之后首先要配的:用户名和邮箱
 git config --global user.email ""
 git config --global user.name ""

Git项目搭建

图片

创建仓库

  1. 创建本地仓库
git init #会在当前路径下创建出一个隐藏文件夹 .git,存放git的版本、管理等信息
  1. 克隆远程仓库
# 克隆一个项目和它的整个代码历史(版本信息)
$ git clone [url]  # https://gitee.com/kuangstudy/openclass.git

配置branch和remote

git branch -M main
git remote add origin https://github.com/hangx969/Scripts.git

更换remote

有时候在一开始添加remote的时候添加的是https url,后面希望更换成ssh url:

# 查看当前remote
git remote -v
# 切换remote
git remote set-url origin git@ssh.xxxx

免密码登录git

  • github设置中加入客户端的ssh公钥。

  • git clone项目下来的时候,使用ssh链接而非http链接。(http链接是需要MFA的,后续每次push都需要输入用户名密码)

image-20241228141528061

  • git remote add采用ssh的link
git remote add origin git@github.com:hangx969/k8s-platform-tools.git
git add .
git commit -m "xxx"
git checkout -b xxx
git push origin xxx

Git分支管理

查看操作

# 查看本地分支
git branch
# 查看远程分支
git branch -r
# 查看本地远程所有分支的汇总
git branch -a

获取远程分支

# 从远程拉取最新的分支/标签信息到本地,顺手清理本地那些"远程已经删除"的origin/xxx分支
git fetch -p

删除本地分支

git branch -d xxx

注意

  • -d:安全删除,只有当这个分支的提交已经被合并到当前分支,或者 Git 认为它的改动不会丢时,才会删。
  • -D:强制删除。如果这个分支上还有没合并的提交,就丢失了。

删除远程分支

git push origin --delete feature-x

工作流程

  • master主分支应该非常稳定,用来发布新版本,一般情况下不允许在上面工作。

  • 工作一般情况下在新建的dev分支上工作,工作完后,比如要发布,或者说dev分支代码稳定后可以合并到主分支master上来。

  • 要将修改后的代码推送到新的分支 "dev" 上,您可以按照以下步骤使用 Git 命令来操作:

  • 首先,确保您的本地仓库是最新的:

git pull origin main  # 假设您从 main 分支拉取的最新代码
  1. 创建并切换到新的分支 "dev":
git checkout -b dev
 这个命令会创建一个名为 "dev" 的新分支,并自动切换到这个分支。
  1. 将您的代码更改添加到暂存区:
git add .
# 或者您可以只添加部分文件
git add <文件路径>
  1. 提交您的更改:
git commit -m "描述您的更改"
  1. 将 "dev" 分支推送到远程仓库:
git push origin dev
 如果是第一次将这个分支推送到远程仓库,Git 会创建远程的 "dev" 分支。

Git版本管理

# 查看代码提交记录
git log
# 查看当前环境文件变更状态
git status
# 查看某个文件的diff
git diff xxx.md
# 撤销单个文件的修改
git checkout - xxx.md
# 撤销所有文件的修改 (谨慎)
git reset --hard
# 回滚到某个commit版本(谨慎)
git reset --hard <commit-id>
# 回滚完之后push会被拒绝,加force(谨慎)
git push origin feature-x --force

开发常用流程

初始化本地仓库并拉取远程仓库

  • 本地新建文件夹

  • 文件夹内打开git bash

git init
git remote add origin <git repo link>
git pull --rebase origin main

本地更改推送到远程仓库

  • 添加所有文件到暂存区
git add .

# 如果只提交部分文件
git status
git add xxx.yaml 
  • 提交暂存区的内容到本地仓库
git commit -m "comments"
  • 新建分支
git checkout -b aaa-bbb-ccc
  • 提交至远程仓库的新分支
git push origin aaa-bbb-ccc
# git -u: 第一次加了参数-u后,以后即可直接用git push代替git push origin main
  • 忽略文件: 被忽略的文件在 .gitignore里面,可以配置正则规则配置哪些文件不会被push到远端仓库

Merge PR

推送新分支到远程仓库之后,在远程仓库上会执行创建Pull Request、Merge Pull Request等操作。

  • 在github上一般采用"Merge and rebase"方式来merge PR:

  • 先对PR分支执行rebase,PR分支的commit会加到main分支的最新状态上

  • 再通过merge commit将PR的更改合并到main上

这样main分支上会保留一个merge commit。

  • azure devops上一般采用"Rebase and fast-forward":

  • 对 PR 的分支执行一个 rebase 操作,将 PR 分支上的提交重新应用到目标分支的最新状态上。

  • 然后通过 fast-forward merge 将这些提交直接添加到目标分支中,不会创建 merge commit。

合并后,目标分支上的提交历史是线性的,PR 的提交直接接在目标分支的最新提交之后。没有额外的 merge commit。

从远程仓库拉取更新内容到本地

本地推送完成新分支之后,远端仓库执行了merge PR操作,这样main分支的提交记录已经改变,需要把新的main分支同步到本地仓库:

git checkout main
git branch -D aaa-bbb-ccc
git pull --rebase origin main

Merge main到开发分支

比如我正在一个dev分支上开发,此时远程main分支有别人提交了新改动,我在dev分支完成开发,push之前,需要同步远程main的改动:

# 1. 确保当前在 dev 分支
git checkout dev

# 2. 获取远程最新代码
git fetch origin

# 3. 合并 main 分支到当前分支
git rebase origin/main # 推荐,保留历史整洁
# 或者
git merge origin/main # 保留完整历史

# 后续可以继续进行commit和push

给commit打tag

有时候我们推送上去的commit需要打一个tag,这个tag往往会代表一个breaking change的标记。推送commit和推送tag分开操作:

# 推送开发分支
git checkout -b feat/xxx
git add .
git commit -m "update for breaking changes"
git push origin feat/xxx
# 远程仓库先merge
# 切到main打tag再单独推送tag
git checkout main
git pull --rebase origin main
git tag 0.4.1
git push origin 0.4.1

清理操作

清除github repo的commit记录

#进入本地仓库的目录
cd /path/to/your/repository
#创建并切换到一个与当前分支无关的新的孤立分支。
git checkout --orphan newBranch
# 添加所有的文件到新分支
git add .
# 提交更改
git commit -am "Initial commit"
# 删除原始分支:因为我们将删除commit历史记录,所以不再需要原始分支
git branch -D main
# 将新的分支重命名为 main 分支
git branch -m main
# 强制将本地修改推送到远程仓库:由于我们已经改变了本地仓库的历史记录,所以必须使用强制推送选项
git push -f origin main

清除所有git history并覆盖到远端仓库

cd <repo dir>
rm -rf ./git
git init
git config user.name "xxxx"
git config user.email "xxxxx"
git add .
git commit -m "initial commit with cleaned history"
# git branch -M main
git remote add origin <remote-repo-url> # 去github repo上copy ssh url
git push -f origin main

此操作会覆盖掉远程仓库的历史记录。完成之后,repo就只会有一个干净的提交记录,所有历史记录都会被清除。

清理被gitignore的文件

有时候有些文件已经被git add和commit了,但是后面在gitignore中配置忽略他们。这时需要重新track文件:

# 先更改.gitignore文件
# 取消track
git rm --cached -r Python/python-manuscripts
# 重新commit,移除这些文件
git commit -m "Stop tracking Python/python-manuscripts files"
# 重新add和commit
git add .
git commit -m "re-track files"
git push origin main
# 完成后,git将不再追踪这些后来配置的忽略文件

git配置代理

git代理

# 设置
git config --global http.proxy 'socks5://127.0.0.1:7890' 
git config --global https.proxy 'socks5://127.0.0.1:7890'

# 恢复
git config --global --unset http.proxy
git config --global --unset https.proxy

git clone ssh设置代理

vim ~/.ssh/config
# 全局
# ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p
# 只为特定域名设定
Host github.com
    ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p

实用git工具

  • GitKraken
  • tortoisegit (windows平台)
  • fork
  • sourceTree
  • git graph (vscode插件)
  • 学习git:Learn Git Branching
  • alias: zsh自带git插件

Lab

# 基于以下内容创作本代码,用于讲解DevOps精讲课Git与Github章节
# https://www.bookstack.cn/read/backlog-git-tutorial/35.md
# https://www.youtube.com/watch?v=tRZGeaHPoaw&ab_channel=KevinStratvert

# 常用命令
git XXX -h # 询问git XXX是啥意思
git help XXX # 更详细的解释,打开一个本地html页面给你解释
git log --oneline # 看看我之前所有的commit历史(以简洁的形式)
git reset XXX # 回溯到某个commit上

# 事前预备
git config --global user.name "xxxx"
git config --global user.email "xxxx"
git init # 初始化文件夹
# 创建一个myfile.txt文件
git status # 这是我们超常用的命令
git add myfile.txt # git add . 可以track所有目录里文件
git commit -m "first commit" # 此时此刻,照张照片,贴在相册,可以回溯

# 建立分支
git branch issue1 # 建立一个名为issue1的分支
git branch # 查看所有分支,发现head还指在master

# 切换分支
git checkout issue1 # 切换到issue1分支,-b可以创建分支并切换,git switch -c是创建并切换(推荐)
# 修改myfile.txt文件
git add myfile.txt
git commit -m "add new content"
# 现在master落后于issue1,head指向issue1

# 合并分支 -- merge合并都是先在主线上,把其他分支往主线上合并
# 先切换master分支,然后把issue1分支导入到master分支
git checkout master
# 打开myfile.txt档案以确认内容,应该是旧的
git merge issue1 
# 打开myfile.txt档案以确认内容,应该是新的,此时head在master上

# 删除分支
# 既然issue1分支的内容已经顺利地合并到master分支了,现在可以将其删除了
git branch -d issue1 # 删除了issue1分支
git branch # 检查现在只有master分支了

# 并行操作
# 首先创建issue2分支和issue3分支,并切换到issue2分支
git branch issue2
git branch issue3
git checkout issue2
git branch
# 在issue2分支的myfile.txt添加commit命令的说明后提交
git add .\myfile.txt
git commit -m "add content from issue2"
# 接着,切换到issue3分支
git checkout issue3
# 打开myfile.txt档案,由于在issue2分支添加了新内容,所以issue3分支的myfile.txt里没有新内容
git add myfile.txt
git commit -m "add content from issue3"
# 对比切换到issue2和issue3分支

# 解决合并的冲突
# 把issue2分支和issue3分支的修改合并到master
# 切换master分支后,与issue2分支合并
git checkout master
git merge issue2 # 没有冲突,这是fast-forward合并
git merge issue3 # 出现冲突报警,由于在同一行进行了修改,所以产生了冲突
# 打开myfile.txt文件,处理冲突
git add myfile.txt
git commit -m "merge issue3"

# 用rebase合并
# 合并issue3分支的时候,使用rebase可以使提交的历史记录显得更简洁
# 现在暂时取消刚才的合并
git reset --hard HEAD~
# 画图解释目前的历史记录
# 切换到issue3分支后,对master执行rebase
git checkout issue3
git rebase master
# 和merge时的操作相同,修改在myfile.txt发生冲突的部分
# rebase的时候,修改冲突后的提交不是使用commit命令,而是执行rebase命令指定 —continue选项
# 若要取消rebase,指定 —abort选项
git add myfile.txt
git rebase --continue # 按i进入insert模式,输入comment,Esc后:wq保存并退出
# 这样,在master分支的issue3分支就可以fast-forward合并了,切换到master分支后执行合并
git checkout master
git merge issue3
# myfile.txt的最终内容和merge是一样的,但是历史记录拉成直线了

# 创建Github Repo
# 先在Github上创建一个Repo,创建完后,下面三行是自动生成的
git remote add origin https://github.com/cengxiye/learn-git-2024.git # 首先将远程仓库的地址添加到本地仓库,并将其命名为 "origin"
git branch -M main # 将当前分支(默认是master)重命名为main
git push -u origin main # 将本地的"main"分支推送到远程仓库"origin"。-u参数用于设置"origin"作为默认的远程仓库
# 以后可以直接使用git push而不需要指定远程仓库和分支。这个命令会将本地的"main"分支的内容推送到远程仓库,实现同步


# 简要介绍Github
# About,填写有关你这个Repo的信息
# Issue,提交bug和feature request,可以assign给其他人/可以加labels等等
# Actions,可以run test,测试你得代码
# Projects,可以管理你的项目
# Wiki,类似一个有关于你项目的百科
# Security,诸如代码安全扫描等
# Insights,查看项目数据
# Settings,设置你的项目
# Release,回到你Repo的主页,可以发布1.0版本你的release,你的代码被打包成zip

# PUSH
# 在main branch上,修改mytext.txt文件
git add .\myfile..txt
git commit -m "change file, to be pushed to origin"
git push
# 检查Github上的文件,已经更新

# PULL
# 在Github上修改txt
git pull
# 在本地main branch上,检查txt

# FETCH
# 在Github上修改txt
git fetch 
git diff main origin/main
# git fetch:只是将远程仓库的变更下载到本地,不会自动合并到当前分支
# git pull = git fetch + git merge

# CLONE
git clone https://github.com/cengxiye/sid.github.io.git
# 适用场景:使用 git clone 通常是在开始新项目或者从头开始的时候
# 适用场景:使用 git pull 通常是在你已经有了一个本地仓库,希望获取最新变更时
# 操作对象:git clone 操作的对象是整个远程仓库
# 操作对象:git pull 操作的对象是当前分支上的远程变更
# 执行时机:git clone 只需要执行一次,创建本地仓库的拷贝
# 执行时机:git pull 需要在你想要获取远程仓库变更时执行

Troubleshooting

大文件上传报错

  • 目录中有大文件,push的时候显示超过100MB不能上传,删除这个文件之后,push仍然会提交这个文件报错。

  • 解决:要从所有commit中把这个文件删掉,再push

git filter-branch --force --index-filter "git rm --cached --ignore-unmatch xxx/xxx.exe" --prune-empty --tag-name-filter cat -- --all
  • git pull时如果遇到报错
fetch-pack: unexpected disconnect while reading sideband packet
fatal: early EOF
fatal: fetch-pack: invalid index-pack output
  • 原因:远程仓库的文件过大,需要设置本地仓库大小。

  • 解决:

git config http.sslVerify "false"
#若出现下列错误:
git config http.sslVerify "false" fatal: not in a git directory
#再继续执行即可解决
git config --global http.sslVerify "false"
#文件大小的上限设置:
git config --global http.postBuffer 524288000

22端口连接报错

以Windows系统为例进行说明,在个人电脑上使用Git命令来操作GitHub上的项目,本来都很正常,突然某一天开始,会提示如下错误ssh: connect to host github.com port 22: Connection Timeout

  1. 先查看下具体ssh报错:
ssh -vT git@github.com
  1. 结果会显示使用的ssh配置文件,以及连接github 22端口超时:
OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2
debug1: Reading configuration data C:\\Users\\xuhan/.ssh/config
debug1: C:\\Users\\xuhan/.ssh/config line 1: Applying options for github.com
debug1: Connecting to ssh.github.com [20.205.243.160] port 22.

SSH 端口说明

GitHub 的标准 SSH 端口是 22。但在某些网络环境中(公司防火墙、ISP限制等),端口 22 可能被封锁。GitHub 提供了替代方案:通过 ssh.github.com 的 443 端口(HTTPS 端口)来进行 SSH 连接。

  1. 修改这个ssh配置文件,改成连接ssh的443端口:
gsudo notepad C:\\Users\\xuhan/.ssh/config

Host github.com
  Hostname ssh.github.com
  Port 443
  1. 修改完成后测试连接github:
ssh -T git@github.com

结果显示:`Hi xxxxx! You've successfully authenticated, but GitHub does not provide shell access.就表示一切正常了。

  1. 再次尝试上传下载github文件,结果显示正常:
git add .
git commit -m "Update"
git push origin main

多GitHub账号SSH密钥冲突导致push报错

问题背景

在同一台机器上配置了多个 GitHub 账号的 SSH 密钥(例如工作账号 ds-hangxu 和个人账号 hangx969),push 个人仓库时报错:

ERROR: Permission to hangx969/learning-notes.git denied to ds-hangxu.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists.

原因是 ~/.ssh/configHost github.com 下配置了多个 IdentityFile,SSH 连接时优先使用了第一个密钥(工作账号),GitHub 将你识别为工作账号用户,而该用户没有个人仓库的权限。

解决办法

  1. 修改 ~/.ssh/config,为不同账号配置不同的 Host 别名,并加上 IdentitiesOnly yes 确保只使用指定密钥:
# 工作账号
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_ds_github
    IdentitiesOnly yes

# 个人账号
Host github-personal
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
  1. 将个人仓库的 remote URL 改为使用别名:
cd ~/github-repo/learning-notes
git remote set-url origin git@github-personal:hangx969/learning-notes.git
  1. 验证连接身份:
# 验证工作账号
ssh -T git@github.com
# Hi ds-hangxu! You've successfully authenticated...

# 验证个人账号
ssh -T git@github-personal
# Hi hangx969! You've successfully authenticated...
  1. 推送测试:
git push origin main

关键点

  • IdentitiesOnly yes 是关键配置,确保 SSH 只使用指定的密钥文件,不会尝试 ssh-agent 中的其他密钥。
  • Host 别名(如 github-personal)仅用于 SSH 路由,Git 实际连接的仍然是 github.com
  • 所有使用个人账号的仓库都需要将 remote URL 中的 github.com 替换为 github-personal