Git学习

Git学习

我的感觉就是可以把代码保存到一个地方,供自己和团队修改

Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。

Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。

一般工作流程如下:

  • 克隆 Git 资源作为工作目录。
  • 在克隆的资源上添加或修改文件。
  • 如果其他人修改了,你可以更新资源。
  • 在提交前查看修改。
  • 提交修改。
  • 在修改完成后,如果发现错误,可以撤回提交并再次修改并提交。

基础配置

Git的配置设计system,global以及local.分别对于所有用户,当前用户的所有仓库以及当前仓库

1
2
3
4
5
6
7
git config --global user.name
git config --global user.email
git config --global core.editor
git config --global -e
git config --global core.autocrlf = true #替换由于不同OS导致的换行符 win
git config --global core.autocrlf = input #替换由于不同OS导致的换行符 mac
git log --oneline --reverse #查看历史

创建.gitignore忽略文件暂存,github上有文件模板

基本概念

  • 工作区:就是你在电脑里能看到的目录。
  • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
  • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

image-20210720214140606

1
2
3
4
5
6
7
8
9
git init
#使用当前目录,初始化一个Git仓库
git init newrepo
#指定目录
git clone <repo>#克隆一个仓库
git clone <repo> <directory>#将仓库克隆到一个指定目录
git config#git设置信息
git config--global user.name "runoob"
git config --global user.email test@run

HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。

image-20210801131105094

git status查看仓库状态,查看工作区有无更新与未被追踪的文件

image-20210801135729377

修改后还未git add更新到暂存区(stage)以及还未track的文件使用git add添加

git log 查看提交日志 查看提交的信息 message与提交人以及时间 在后面加上指定的文件或目录可以只显示所要求的信息

image-20210801131231033

commit旁是一串哈希值

git log -p 查看提交带来的变动,文件前后的差别

git diff 查看工作树,暂存区,最新提交之间的差别

image-20210801132603359

+表示增加一行 -表示减少一行

提交后,输入git diff没有输出 因为工作区和暂存区无差别

若要查看与最新提交的差别,需要git diff HEAD

image-20210801134601328

可以在git commit之前先执行git diff HEAD查看工作区与最新提交的差别,等确认玩别再进行提交

HEAD指向当前分支中最新一次提交的指针

分支操作

master分支是Git默认创建的分支,所有分支都是以这个分支为中心进行

git branch显示分支

image-20210801140430235

*号表示当前分支

1
git checkout -b A 创建,切换分支  #-b表示创建

实际上与者两条指令作用相同

1
2
3
4
5
git branch A#chuangjian

git checkout A #转到A

git checkout - #切换到上次的分支

以当前的master分支为基础创建新的分支

1
git commit -m  "feature-A" #提交

执行git add就会将代码提交到A分支,而master分支不会受影响.这样可以在不互相影响的情况下同时进行多个功能的开发

image-20210801142740455

1
git merge #    合并分支 

将分支合并到主干分支master中,首先切换到master分支.为了在历史记录中明确记录下本次分支合并,我们需要创建合并提交.在合并时加上—no-ff参数随后录入合并提交的信息

默认信息中已经包含了是从feature-A分支合并过来的相关内容,所以可不做任何更改

fast-forward

Git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,叫做“快进”(fast-forward)不过这种情况如果删除分支,则会丢失merge分支信息。

–squash

把一些不必要commit进行压缩,比如说,你的feature在开发的时候写的commit很乱,那么我们合并的时候不希望把这些历史commit带过来,于是使用–squash进行合并,此时文件已经同合并后一样了,但不移动HEAD,不提交。需要进行一次额外的commit来“总结”一下,然后完成最终的合并。

–no-ff

关闭fast-forward模式,在提交的时候,会创建一个merge的commit信息,然后合并的和master分支
merge的不同行为,向后看,其实最终都会将代码合并到master分支,而区别仅仅只是分支上的简洁清晰的问题,然后,向前看,也就是我们使用reset 的时候,就会发现,不同的行为就带来了不同的影响

1
git log --graph  #以图表形式查看提交

image-20210801150741989

更改提交

1
git reset #回溯历史版本

要让仓库的head,暂存区与当前工作树回溯到之前状态,需要用到git reset —hard 提供目标时间点的哈希值就可以完全恢复至改时间点的状态

回溯至之前状态后,利用git reflog可以前推

1
2
3
git commit --amend #可以修改上一条提交信息
git commit -am "" #不用add直接提交
git rebase -i HEAD~2 #合并历史 选定当前分支包含HEAD在内的两个最新历史记录

Learn Git Branching

1)、在本地创建一个版本库(即文件夹),通过git init把它变成Git仓库;

2)、把项目复制到这个文件夹里面,再通过git add .把项目添加到仓库;

3)、再通过git commit -m "注释内容"把项目提交到仓库;

4)、在Github上设置好SSH密钥后,新建一个远程仓库,通过git remote add origin 远程仓库地址将本地仓库和远程仓库进行关联;

5)、最后通过git push -u origin master把本地仓库的项目推送到远程仓库(也就是Github)上。

更新

工作流

1.git clone // 到本地
2.git checkout -b xxx 切换至新分支xxx
(相当于复制了remote的仓库到本地的xxx分支上
3.修改或者添加本地代码(部署在硬盘的源文件上)
4.git diff 查看自己对代码做出的改变
5.git add 上传更新后的代码至暂存区
6.git commit 可以将暂存区里更新后的代码更新到本地git
7.git push origin xxx 将本地的xxxgit分支上传至github上的git
(如果在写自己的代码过程中发现远端GitHub上代码出现改变)
1.git checkout main 切换回main分支
2.git pull origin master(main) 将远端修改过的代码再更新到本地
3.git checkout xxx 回到xxx分支
4.git rebase main 我在xxx分支上,先把main移过来,然后根据我的commit来修改成新的内容
(中途可能会出现,rebase conflict 手动选择保留哪段代码)
5.git push -f origin xxx 把rebase后并且更新过的代码再push到远端github上
(-f 强行)
6.原项目主人采用pull request 中的 squash and merge 合并所有不同的commit
远端完成更新后
1.git branch -d xxx 删除本地的git分支 git push —delete
2.git pull origin master 再把远端的最新代码拉至本地

git的撤销操作

git分为Disk,staging,local,remote.

git restore(git checkout),git reset(git restore —staged),git revert HEAD.

在磁盘上文件修改后,git diff查看差别,git status查看状态.要撤销磁盘上的改动,git restore 撤销.

git add提交到暂存区,git status状态改变,要撤销git reset 或git restore —staged .

若还要撤销磁盘上的修改以及暂存区,git checkout HEAD .

git commit提交到本地git,git reset —soft HEAD~1 回退一个commit. git reset HEAD~1回退一个commit以及暂存区回退,只保留磁盘修改,git reset —hard HEAD~1磁盘修改也会退. git revert HEAD增加一个commit与之前的commit相反,用于主分支.

git push推送到远程

Git 使用规范流程 - 阮一峰的网络日志 (ruanyifeng.com)

Git 工作流程 - 阮一峰的网络日志 (ruanyifeng.com)

git checkout

切换分支或者到某个commit历史,后者会出现detach head状态

1
2
3
4
5
git checkout  <file_name> #丢弃工作区的修改,并用最近一次的commit内容还原到当前工作区(对文件中内容的操作,无法对添加文件、删除文件起作用)

git checkout HEAD^ <file_name> #将指定commit提交的内容(HEAD^表示上一个版本)还原到当前工作区

git checkout <branch_name> <file_name> #将指定分支的指定提交内容还原到当前分支工作区

git restore

1
2
3
git restore --staged <file_name> #将暂存区的修改重新放回工作区(包括对文件自身的操作,如添加文件、删除文件)

git restore <file_name> #丢弃工作区的修改(不包括对文件自身的操作,如添加文件、删除文件

也就是说git restore主要针对工作区内容

git reset

移动HEAD指针与分支指向,同时有—soft,—mixed,—hard选项.

—hard

清空暂存区,将已提交的内容的版本恢复到本地,本地的文件也将被恢复的版本替换(恢复到上一次commit后的状态,上一次commit后的修改也丢弃)
清空上一次的commit记录,本地文件也会恢复到之前的记录状态

git revert

git rm

除了与原本rm命令有相同作用外,还可以删除已经存到暂存区的文件.

1
git rm --cached

查看分支图

1
git log  --graph   --oneline --decorate --all

image-20231110121408874

—oneline 日志单行显示
—graph 分支图显示
—decorate 可显示分支名称
—all 显示所有分支

查看stage/index区

1
git ls-files -s

这里再写一点关于Github的,Github对我来说最新以及最有用的东西就是projects和action了.

前者是用于tack repos的,适合团队协作.

image-20231122181504731

后者就不用我多说了,但是有些action不太好写(或者说我没懂).还是要看文档多练

新增

🥡Git 菜单 - 《Git 菜单》 - 书栈网 · BookStack看到的比较好的教程,比较实用,这里简单说一些重点.

1. 查看之前提交的文件

如果要看之前提交过的文件而不是整个commit

1
git checkout <commit> <file>

你可以用它来查看项目之前的状态,而不改变当前的状态。检出文件使你能够查看某个特定文件的旧版本,而工作目录中剩下的文件不变。

缓存区也被替换为相应的文件

2.git submodule

git中包含git的大项目.

添加子module

1
2
git clone <repository> --recursive
git submodule add <submodule_url>
1
2
3
4
git submodule init # 初始化子模块
git submodule update # 更新子模块
# 或者
git submodule update --init --recursive
1
git submodule # 查看所有子模块

3.git stash

临时存储,可以在git checkout等这种命令时使用

1
2
3
4
git stash 
git stash list
git stash apply
git stash pop

git revert vs. git reset vs. git check out

git revert用来撤销一个已经提交的快照。但是,它是通过搞清楚如何撤销这个提交引入的更改,然后在最后加上一个撤销了更改的 提交,而不是从项目历史中移除这个提交。这避免了Git丢失项目历史

1
2
3
4
5
# 编辑一些跟踪的文件
# 提交一份快照
git commit -m "Make some changes that will be undone"
# 撤销刚刚的提交
git revert HEAD

git reset 看做一个 危险 的方式。当你用 git reset 来重设更改时(提交不再被任何引用或引用日志所引用),我们无法获得原来的样子——这个撤销是永远的。使用这个工具的时候务必要小心,因为这是少数几个可能会造成工作丢失的命令之一。

reset包括soft(缓存区和工作目录都不会被改变),mixed( 默认选项。缓存区和你指定的提交同步,但工作目录不受影响),hard(将缓存区和工作目录都重设到这个提交。它不仅清除了未提交的更改,同时还清除了 <commit> 之后的所有提交。)以及

当有 <commit> 之后的提交被推送到公共仓库后,你绝不应该使用 git reset。发布一个提交之后,你必须假设其他开发者会依赖于它.

重点是,确保你只对本地的修改使用 git reset,而不是公共更改。如果你需要修复一个公共提交,git revert 命令正是被设计来做这个的。

岁月史书

我们知道git reset可以修改历史,此外还有git rebase,也是常用在本地仓库的命令.

Git 是通过在选定的基上创建新提交来完成这件事的——它事实上重写了你的项目历史。理解这一点很重要,尽管分支看上去是一样的,但它包含了全新的提交。

1
git rebase <base>

将当前分支 rebase 到 <base>

git rebase -i

-i 标记运行 git rebase 开始交互式 rebase。交互式 rebase 给你在过程中修改单个提交的机会,而不是盲目地将所有提交都移到新的基上。你可以移除、分割提交,更改提交的顺序

  • p, pick = use commit

  • r, reword = use commit, but edit the commit message

  • e, edit = use commit, but stop for amending

  • s, squash = use commit, but meld into previous commit

  • f, fixup [-C | -c] = like “squash” but keep only the previous

    commit’s log message, unless -C is used, in which case keep only this commit’s message; -c is same as -C but opens the editor

  • “合并(squash)”适用于需要简化提交历史和提高代码可读性的场景。通常在将代码推送到共享仓库之前使用。

  • “修复(fixup)”适用于修复之前提交中的错误或问题的场景。通常在意识到错误或问题后使用。
  • squash 会将多个提交合并成一个提交。它会把所有被合并提交的修改合并到一起,并且会弹出一个对话框让你重新编辑合并后的提交信息。

    fixup 会将多个提交合并成一个提交,但是它不会弹出对话框让你重新编辑提交信息。它只会用被合并提交的第一条提交信息

    pipeline

    第一步是在任何和 git rebase 有关的工作流中为每一个 feature 专门创建一个分支。它会给你带来安全使用 rebase 的分支结构

    在你工作流中使用 rebase 最好的用法之一就是清理本地正在开发的分支。隔一段时间执行一次交互式 rebase,你可以保证你 feature 分支中的每一个提交都是专注和有意义的。你在写代码时不用担心造成孤立的提交

    git merge-base 命令非常方便地找出 feature 分支开始分叉的基

    1
    git merge-base feature master

    你可以选择将这个分支 rebase 到 master 分支之后,或是使用 git merge 来将这个功能并入主代码库中。

    这和将上游改动并入 feature 分支很相似,但是你不可以在 master 分支重写提交,你最后需要用 git merge 来并入这个 feature。但是,在 merge 之前执行一次 rebase,你可以确保 merge 是一直向前的,最后生成的是一个完全线性的提交历史。这样你还可以加入 Pull Request 之后的提交。

    简单来说,可以在本地分支上使用rebase将feature分支移到main上,然后使用merge将代码并入远程代码库中.

    git钩子

    Git钩子

    钩子存在于每个 Git 仓库的 .git/hooks 目录中。当你初始化仓库时,Git 自动生成这个目录和一些示例脚本。

    只需添加一个文件名和上述匹配的新文件,去掉 .sample 拓展名

    内置的脚本大多是 shell和 PERL 语言的,但可以使用任何脚本语言,只要它们最后能编译到可执行文件。

    每次脚本中的 #!/bin/sh 定义了你的文件将被如何解释。比如,使用其他语言时你只需要将 path 改为你的解释器的路径。

    比如说,可以在 prepare-commit-msg 中写一个可执行的 Python 脚本。下面这个钩子和上一节的 shell 脚本做的事完全一样。

    1
    2
    3
    4
    5
    #!/usr/bin/env python
    import sys, os
    commit_msg_filepath = sys.argv[1]
    with open(commit_msg_filepath, 'w') as f:
    f.write("# Please include a useful commit message!")

    还是建议用python这种来写,bash脚本语言对我这种还是有点麻烦了.

    高级git log

    --oneline 标记把每一个提交压缩到了一行中。它默认只显示提交ID和提交信息的第一行

    --decorate 标记让 git log 显示指向这个提交的所有引用(比如说分支、标签等)。

    --stat 选项显示每次提交的文件增删数量,如果你想知道每次提交删改的绝对数量,你可以将 -p 选项传入git log。这样提交所有的删改都会被输出

    --graph 选项绘制一个 ASCII 图像来展示提交历史的分支结构

    git shortlog 是一种特殊的 git log,它是为创建发布声明设计的。它把每个提交按作者分类,显示提交信息的第一行。这样可以容易地看到谁做了什么

    杂鱼

    删除分支

    1
    2
    3
    4
    // 删除本地分支
    git branch -d localBranchName
    // 删除远程分支
    git push origin --delete remoteBranchName
    1
    git merge --no-ff <branch>

    将指定分支并入当前分支,但 总是 生成一个合并提交(即使是快速向前合并)。这可以用来记录仓库中发生的所有合并。

    git cherry-pick 命令「复制」一个提交节点并在当前分支做一次完全一样的新提交。

    git fetch <remote> <branch>

    git commit --amend不只是修改了最新的提交——它进行了一次替换。对于 Git 来说,这看上去像一个全新的提交,永远不要修复一个已经推送到公共仓库中的提交

    git clean 命令将未跟踪的文件从你的工作目录中移除

    Git 用引用日志这种机制来记录分支顶端的更新。它允许你回到那些不被任何分支或标签引用的更改。在重写历史后,引用日志包含了分支旧状态的信息,有需要的话你可以回到这个状态,每次当前的 HEAD 更新时(如切换分支、拉取新更改、重写历史或只是添加新的提交),引用日志都会添加一个新条目

    git submodule

    Git 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录来保存. Git 子模块只是在特定时间快照下对另一个仓库的引用.通过 Git 子模块,Git 仓库可以整合并跟踪外部代码的版本历史.

    Working with Git submodules: A practical guide (with examples) - Sling Academy

    1
    git submodule add 

    这会在指定路径下创建一个新目录,其中包含所添加子模块的内容. 此外,Git 还会在仓库中添加一个 .gitmodules 文件,其中存储了项目 URL 与本地子目录之间的映射关系.

    1
    git submodule init

    这将初始化本地配置文件,而 Git 现在也会意识到有需要签出的子模块。

    1
    git submodule update

    这将获取并更新子模块中的文件,使其与主版本库中描述的版本一致。

    管理子模块涉及各种任务,例如更新到子模块主分支的最新提交:

    1
    2
    git checkout master 
    git pull

    然后在主版本库中提交这一变更,以跟踪子模块的最新提交

    1
    2
    3
    git submodule deinit  
    git rm
    rm -rf .git/modules/

    执行完这些命令后,可以提交更改,以便从项目中完全删除子模块引用。

    • 使用子模块时:在将子模块的变更推送到主项目之前,一定要先推送子模块的变更。
    • 避免在子模块内部添加子模块。 将子模块保留在特定的标记或提交中,以确保稳定性。

    git subtree

    Before/After Git subtree diagram

    git subtree可以让你把一个仓库嵌套在另一个仓库的子目录中.

    Git Subtree 是一种将一个 Git 仓库作为另一个仓库的子目录的方法。它允许你将一个项目嵌入到另一个项目中,同时保持两者的独立性。这在多个项目需要共享代码或者需要管理依赖关系时非常有用。

    使用 Git Subtree 的基本步骤如下:

    1. 添加子树仓库:首先,你需要将子树仓库添加到你的主仓库中。这可以通过 git subtree add 命令来完成。

      1
      git subtree add --prefix=<subdirectory> <repository-url> <branch>:<subdirectory>

      其中 <subdirectory> 是子目录的路径,<repository-url> 是子树仓库的 URL,<branch> 是子树仓库的分支名。

    2. 拉取子树更新:如果子树仓库有更新,你可以使用 git subtree pull 命令来更新你的主仓库中的子目录。

      1
      git subtree pull --prefix=<subdirectory> <repository-url> <branch>
    3. 推送子树更改:如果你对子目录做了更改,并且想要将这些更改推送回子树仓库,可以使用 git subtree push 命令。

      1
      git subtree push --prefix=<subdirectory> <repository-url> <branch>
    4. 删除子树:如果不再需要子树,可以使用 git subtree delete 命令来删除它。

      1
      git subtree delete --prefix=<subdirectory>

    参考资料

    1. Git Tutorial for Beginners: Learn Git in 1 Hour - YouTube
    2. https://zhuanlan.zhihu.com/p/665536372
    3. Projects vs. classic projects (linkedin.com)
    4. Creating a MarketPlace GitHub action (linkedin.com)
    5. GitHub 入门文档 - GitHub 文档
    6. 🥡Git 菜单 - 《Git 菜单》 - 书栈网 · BookStack
    -------------本文结束感谢您的阅读-------------
    感谢阅读.

    欢迎关注我的其它发布渠道