### Git * [Pro Git](https://git-scm.com/book/en/v2) * [MIT 6.NULL - git](https://missing.csail.mit.edu/2020/version-control/) * [Learn Git Branching](https://learngitbranching.js.org/?locale=en_US) #### VCS(version control system) ##### Git的理念 * distributed version control system (DVCS) * VCS: 简单的管理,例如RCS * CVCS(centralized VCS) * DVCS: 每个clone都是一个backup,能处理不同的remote * 操作模式 * 本地操作、集中下载和上传 * The Platonic ideal is that each commit should compile and should move steadily towards more and more tests passing. * ugly interface and beautiful design -> bottom-up地理解git This [XKCD comic](https://xkcd.com/1597/) captures Git’s reputation: git ##### Git's data model * Snapshots: 文件是blob,文件夹是tree,snapshot是top-level tree * Modeling history: relating snapshots * a history is a directed acyclic graph (DAG) of snapshots * 一个snapshot可能有多个parent,比如merge * snapshot被称作commit,是Git的精髓所在,不像其它VCS针对每个文件储存changes * Data model, as pseudocode * Objects and content-addressing * id由SHA-1 hash生成,40个十六进制字符 * `git cat-file -p` 显示对象信息 * References:照顾可读性,和对象不同,它是mutable的 * `master`表示主分支的最近commit * `HEAD`表示“where we currently are” * repositories = objects + references ```python // a file is a bunch of bytes type blob = array // a directory contains named files and directories type tree = map // a commit has parents, metadata, and the top-level tree type commit = struct { parent: array author: string message: string snapshot: tree } type object = blob | tree | commit objects = map def store(object): id = sha1(object) objects[id] = object def load(id): return objects[id] references = map def update_reference(name, id): references[name] = id def read_reference(name): return references[name] def load_reference(name_or_id): if name_or_id in references: return load(references[name_or_id]) else: return load(name_or_id) ``` ##### Staging area areas * staging area也称作index * 和`git add`相关 * Git tracks changes to a developer’s codebase, but it’s necessary to stage and take a snapshot of the changes to include them in the project’s history. `git add` performs staging, the first part of that two-step process. Any changes that are staged will become a part of the next snapshot and a part of the project’s history. Staging and committing separately gives developers complete control over the history of their project without changing how they code and work. * 意义在于摆脱snapshot和当前状态的绝对联系,使commit操作更灵活 * checkout在working directory之间切换;reset从staging area回复到working directory #### Git command-line interface ##### Basics - `git help `: get help for a git command - `git command -h`: concise help - [freenode](https://freenode.net/) 的`#git`和`#github`频道寻求帮助 - `git init`: creates a new git repo, with data stored in the `.git` directory - `git status`: tells you what's going on - `git status -s`: 左列是staging area,右列是working tree - `git add `: adds files to staging area - 对于同一文件,add之后有新改动要重新add - `-p` 添加每个变化前,都会要求确认。对于同一个文件的多处变化,可以实现分次提交 - `git commit`: creates a new commit - Write [good commit messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) and Even more reasons to write [good commit messages](https://chris.beams.io/posts/git-commit/): 大写开头,祈使句,简短 - saves the snapshot to the project history and completes the change-tracking process. In short, a commit functions like taking a photo. Anything that’s been staged with `git add` will become a part of the snapshot with `git commit`. - `git commit -am "m"`可以先add再commit,**commit的文件都是tracked状态** - `-v`显示diff信息 - `git log`: shows a flattened log of history - `git log --pretty=format:"%h %s" --all --graph --decorate --no-merges`: visualizes history as a DAG - `git shortlog/ git log --oneline`: 只显示标题 - `-p`显示全部信息,`-3`显示三条 - `--stat`显示统计信息 - `-S function_name --since=2.weeks --before="2008-11-01" --grep --author --committer --no-merges` - `--all`显示所有branch信息, 或者指定特定的branch - `git diff`: 比较working directory和staging area - `git diff --staged':比较staging area和last commit - `git diff `: show differences since the last commit - `git diff `: shows differences in a file between snapshots - `git difftool`,图形界面 - Diff without dots / with two dots / with three dots: https://matthew-brett.github.io/pydagogue/git_diff_dots.html - `git diff > tmp.diff` 编辑器可显示syntax - `git checkout `: updates HEAD and current branch - `gco $file_path ` 用于回退特定文件 - `git revert` - `-m 1或2` 对于有多个上游commit的commit,用来指定具体revert到哪一个commit - `git rm file` - `git rm --cached`,只删除index,不删除working tree - `git rm log/\*.log` ,通配符,注意要加`\`,Git有自己的文件名拓展 - `git tag -l "v1.8.5*"`: `-l`是为了通配符匹配 - Annotated Tags: `git tag -a v1.4 () -m "my version 1.4"` - Lightweight Tags: `git tag v1.4-lw` - `git push origin /--tags`,需要单独push, `--follow-tags`只push annotated tags ```shell git tag -d v1.4-lw git push origin :refs/tags/v1.4-lw git push origin --delete ``` - `git show /` * `git mv file_from file_to` ```shell mv README.md README git rm README.md git add README ``` ##### Branching and merging 思考git的原理,commit的组成 branch的使用方式:1.Long-Running Branches 2.Topic Branches branch的成本极低,instantaneous - `git branch`: shows branches - `-v -r` verbose,显示远程分支, `-a`显示所有本地和远程 - `--merged` 和`--no-merged` - `git branch `: creates a branch - `git branch ` - `git branch -d` 删除branch - `git checkout -b `: creates a branch and switches to it - same as `git branch ; git checkout ` - `git checkout --` 切换到上一个分支 - checkout需要所有changes已经commit,或者参考[Stashing and Cleaning](https://git-scm.com/book/en/v2/ch00/_git_stashing) - `git checkout `会进入detached HEAD状态,做的commit只属于这一个commit - `git checkout large-mr-branch -- path/to/your/file.ext` - 拆MR - `git checkout --track origin/serverfix`: 创建本地分支并track远程分支 - `git merge `: merges into current branch, merge的不同策略如下: * Fast-forward,直接在当前commit上遍历即可 * recursive strategy: three-way merge * 如果发生冲突,手动修改unmerged files然后add ,或者用mergetool - `git mergetool`: use a fancy tool to help resolve merge conflicts - `git rebase`: rebase set of patches onto a new base - `git rebase ` rebase命令找到共同的祖先节点,然后按顺序replay commits ```shell git checkout experiment git rebase master # <=> git rebase master experiment git checkout master git merge experiment ``` `git rebase --onto master server client` This basically says, “Take the `client` branch, figure out the patches since it diverged from the `server` branch, and replay these patches in the `client` branch as if it was based directly off the `master` branch instead.” It’s a bit complex, but the result is pretty cool. Do not rebase commits that exist outside your repository and that people may have based work on. 会发生混淆,尤其是别人能重新把已经被rebase消除掉的commits push上来的时候,可能会fetch到存在冲突的experiment分支 * 如果遇到了这种问题,可以`git fetch & git rebase experiment`或直接`git pull --rebase` 总结:`rebase`和`filter-branch`本质上是在tell storys,呈现给读者可读性更强的历史记录 ##### Remotes tracking branch的缩写:`@{u}或@{upstream}` - `git remote`: list remotes - `-v` - `git remote add `: add a remote - name本质上是reference - `git remote show/rename/rm ` - `git ls-remote ` - 如何更新fork仓库的代码 - ```shell git remote -v git remote add upstream git@github.com:xxx/yyy.git git fetch upstream git merge upstream/master ``` - `git push :`: send objects to remote, and update remote reference * `git push origin lab1:lab1` * `git push --set-upstream origin my-branch`,本地关联远程分支,用来省略上面一行的分支标注 * `git push origin --delete my-branch` 删除远程分支 - `git branch --set-upstream-to=/`: set up correspondence between local and remote branch - 相当于 `-u` - `git fetch `: retrieve objects/references from a remote - `git fetch origin` ```shell git fetch origin master:tmp git diff tmp git merge tmp git branch -d tmp ``` ```shell git fetch --all git branch -vv git push origin --delete serverfix ``` - `git pull`: same as `git fetch; git merge` `git pull --rebase` and `git rebase --continue` [实用的冲突处理策略](https://juejin.im/post/5d3685146fb9a07ed064f11b) - `git clone`: download repository from remote - 在最后可加文件夹名参数 - `-o` 修改origin name fetch只能得到远程新分支的引用,如果想得到实体: * `git merge origin/serverfix` * `git checkout -b serverfix origin/serverfix` ##### Undo - `git commit --amend`: edit a commit's contents/message, 可把staging area加到上一次commit里 - 可指定文件 - `git reset HEAD `: unstage a file * `git reset HEAD~ `或 `git reset HEAD~1` 撤销上次的commit(会回复到modified状态) * `git reset --hard` * 回到上次commit的版本,配合`git pull/push`(如果file是working directory内的,会很危险) * [Github如何回退敏感信息](https://help.github.com/en/github/authenticating-to-github/removing-sensitive-data-from-a-repository) ```shell # 切到被别人push --force过的分支版本 git fetch --all git reset --hard origin/dev # 回退remote敏感信息 git log git reset --hard XXXXXXXX git push origin HEAD --force ``` - `git checkout -- `: discard changes (很危险的指令) - `git clean -fd` 删掉unstaged文件 ##### Advanced Git * `git diff > patch && git apply patch` - `git cat-file -p`: 显示对象信息 * 40位Hash值,前2位是文件夹,后38位是文件名 * 存在`.git/objects/`中 * [理解git常用命令原理](http://www.cppblog.com/kevinlynx/archive/2014/09/09/208257.html) - `git config`: Git is [highly customizable](https://git-scm.com/docs/git-config) - `/etc/gitconfig`对应`--system` - `~/.gitconfig`或`~/.config/git/config`对应 `--global` - `path/.git/config`对应`--local` - `git config --list (--show-origin)`显示所有config - 设置Identity,见下面Github部分 - `git clone --depth=1`: shallow clone, without entire version history - `git add -p`: interactive staging - `git rebase -i`: interactive rebasing - `git blame`: show who last edited which line - `git blame -L :collection _config.yml` - `git stash`: temporarily remove modifications to working directory - `git stash pop [--index][stash@{id}]` - `git stash pop` 恢复最新的进度到工作区 - `git stash pop --index` 恢复最新的进度到工作区和暂存区 - `git stash pop stash@{1}` 恢复指定的进度到工作区。stash_id是通过git stash list命令得到的。通过git stash pop命令恢复进度后,会删除当前进度 - `git stash save --include-untracked` - `git stash apply stash@{n}` - `git stash list` - `git stash show -p | git apply -R` - `git cherry-pick `: 将指定的提交(的变更)应用到当前分支。 - [利用它只pull request一个特定的commit](https://www.iteye.com/blog/bucketli-2442195) - `git cherry-pick commit1..commit2`:应用从 commit1 (不含) 到 commit2 (含) 的所有提交。 - **处理合并提交 (Merge Commit)**: 直接 `cherry-pick` 一个合并提交会失败,因为 Git 不知道应该采用哪个父分支的变更。 - **解决方案**: 使用 `-m` (mainline) 选项指定“主线”。 1. **查看父提交**: `git show `,输出中会包含 `Merge: `。 2. **确定主线**: 父提交1通常是**被合并的目标分支** (如 `master`),父提交2是**来源分支**。我们通常想应用来源分支的变更。 3. **执行命令**: `git cherry-pick -m 1 `。此命令意为“将该合并提交与它的第一个父提交进行比较,然后将差异应用到当前分支”。 - **注意**: 操作前最好确保不在“分离头指针”(`detached HEAD`)状态,可以先 `git checkout -b ` 创建一个新分支,否则 `cherry-pick` 产生的新提交可能会丢失。 - `git bisect`: binary search history (e.g. for regressions) - [`git ls-files`](https://git-scm.com/docs/git-ls-files) - `git submodule add /path`: 添加子模块。 - `git submodule update`: 更新子模块,使其与主项目的记录匹配。 * `--init`: 初始化尚未克隆的子模块。 * `--recursive`: 递归更新所有嵌套的子模块。 * **常用组合**: `git submodule update --init --recursive` 是克隆一个带子模块的仓库后,最常用的初始化命令。 * `--remote`: **拉取最新代码**。此选项会忽略主项目记录的 commit SHA-1,转而检出子模块远程跟踪分支的最新 commit。这对于始终希望使用子模块最新版本的场景非常有用。 * `--force`: **强制覆盖**。此选项会强制检出子模块,丢弃所有本地的修改。这在需要将子模块重置为干净状态时非常有用,但操作具有破坏性,需谨慎使用。例如:`git submodule update --force --recursive`。 - `git submodule deinit -f .`: 反初始化子模块。 - **其他技巧与注意事项**: * 移除submodule:https://stackoverflow.com/questions/1260748/how-do-i-remove-a-submodule/36593218#36593218 * 如果报错 `already exists in the index`,用 `git rm -r --cached /path` 解决此问题。 * `submodule` 很适合和[dotfiles](https://github.com/huangrt01/dotfiles)搭配,但如果用在项目里可能[出现问题](https://codingkilledthecat.wordpress.com/2012/04/28/why-your-company-shouldnt-use-git-submodules/),尤其是需要 commit 模块代码的时候。 * [使用时可能遇到的坑的集合](https://blog.csdn.net/a13271785989/article/details/42777793) * commit 时有坑,需要先 commit 子模块,再 commit 主体,参考:https://stackoverflow.com/questions/8488887/git-error-changes-not-staged-for-commit - `.gitignore`: [specify](https://git-scm.com/docs/gitignore) intentionally untracked files to ignore - `.gitignore_global`,[我的设定](https://github.com/huangrt01/dotfiles/blob/master/gitignore_global) ``` # ignore all .a files *.a # but do track lib.a, even though you're ignoring .a files above !lib.a # only ignore the TODO file in the current directory, not subdir/TODO # /TODO # ignore all files in any directory named build # build/ # ignore doc/notes.txt, but not doc/server/arch.txt # doc/*.txt # ignore all .pdf files in the doc/ directory and any of its subdirectories # doc/**/*.pdf ``` - Git and GPG: my security [notes](https://github.com/huangrt01/CS-Notes/blob/master/Notes/Output/Security-Privacy-Cryptography.md) #### Miscellaneous - **GUIs**: there are many [GUI clients](https://git-scm.com/downloads/guis) out there for Git. We personally don't use them and use the command-line interface instead. - **Shell integration**: it's super handy to have a Git status as part of your shell prompt ([zsh](https://github.com/olivierverdier/zsh-git-prompt), [bash](https://github.com/magicmonty/bash-git-prompt)). Often included in frameworks like [Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh). - **Editor integration**: similarly to the above, handy integrations with many features. [fugitive.vim](https://github.com/tpope/vim-fugitive) is the standard one for Vim. - `:Gblame`, `:Gbrowse`很方便,需要装[rhubarb.vim](https://github.com/tpope/vim-rhubarb) - `:Git`直接打开`hub` - **Workflows**: we taught you the data model, plus some basic commands; we didn't tell you what practices to follow when working on big projects (and there are [many](https://nvie.com/posts/a-successful-git-branching-model/) [different](https://www.endoflineblog.com/gitflow-considered-harmful) [approaches](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)). - **GitHub**: Git is not GitHub. GitHub has a specific way of contributing code to other projects, called [pull requests](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). - **Other Git providers**: GitHub is not special: there are many Git repository hosts, like [GitLab](https://about.gitlab.com/) and [BitBucket](https://bitbucket.org/). - 自建 / 内网场景的选项与对比(Gitea、Forgejo、GitLab)见 [Software-Engineering:开发协作工具链](./Software-Engineering.md#开发协作工具链github--gitlab--gitea--sonarcloud--1password--confluence--jira--netlify)。 #### 和Github联动 * GitHub is a Git hosting repository that provides developers with tools to ship better code through command line features, issues (threaded discussions), pull requests, code review, or the use of a collection of free and for-purchase apps in the GitHub Marketplace. * [用SSH连GitHub](https://help.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) ```shell git config --global user.name "huangrt01" git config --global user.email huangrt01@163.com # MacOs ssh-keygen -t rsa -b 4096 -C "huangrt01@163.com" # 如果rsa已占用,可用ssh-keygen -t ed25519 -C "huangruiteng@xxx.com" eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa --apple-use-keychain # MacOS带 pbcopy < ~/.ssh/id_rsa.pub # MacOS , Linux用xclip 上github添加SSH Key ssh -T git@github.com # ssh-keygen -y -f ~/.ssh/id_rsa # Linux ``` Github被墙 * `sudo vim /etc/hosts` ``` # GitHub Start 192.30.253.112 github.com 192.30.253.119 gist.github.com 151.101.184.133 assets-cdn.github.com 151.101.184.133 raw.githubusercontent.com 151.101.184.133 gist.githubusercontent.com 151.101.184.133 cloud.githubusercontent.com 151.101.184.133 camo.githubusercontent.com 151.101.184.133 avatars0.githubusercontent.com 151.101.184.133 avatars1.githubusercontent.com 151.101.184.133 avatars2.githubusercontent.com 151.101.184.133 avatars3.githubusercontent.com 151.101.184.133 avatars4.githubusercontent.com 151.101.184.133 avatars5.githubusercontent.com 151.101.184.133 avatars6.githubusercontent.com 151.101.184.133 avatars7.githubusercontent.com 151.101.184.133 avatars8.githubusercontent.com # GitHub End ``` * 改电信DNS: `101.226.4.6`, ` 218.30.118.6` #### 坑! * 如果设置ssh key后,git push仍然要求输入邮箱密码 * `git remote -v`查看origin使用的是https还是ssh * 如果是https,替换成ssh即可 `git remote set-url origin git@github.com:huangrt01/XXX.git` * `Failed to connect to github 443`这一问题的解决方案 1. `git remote set-url origin git@github.com:huangrt01/XXX.git`, 先把连接方式由https改成ssh 2. 再在`~/.ssh/config`中把ssh的端口22改成https端口443 ```shell Host github.com User huangrt01@163.com Hostname ssh.github.com PreferredAuthentications publickey IdentityFile ~/.ssh/id_rsa Port 443 ``` * 建立仓库 ``` git init git remote add origin git@github.com:huangrt01/dotfiles.git git pull --rebase origin master git push --set-upstream origin master ``` #### Git Worktree Git Worktree 允许在同一仓库的多个分支上同时工作,无需多次克隆仓库。 * 核心概念: * 一个 `.git` 目录可以关联多个工作目录(worktree) * 每个 worktree 对应一个不同的分支或 commit * 避免频繁切换分支的麻烦 * 常用命令: ```shell # 创建新的 worktree,关联到特定分支 git worktree add ../feature-branch feature # 查看所有 worktree git worktree list # 删除 worktree git worktree remove ../feature-branch git worktree prune # 清理已删除的 worktree ``` * 适用场景: * 同时开发多个功能分支 * 紧急修复 bug 不打断当前开发 * 对比不同分支的代码 #### Other Resources - [Oh Shit, Git!?!](https://ohshitgit.com/) is a short guide on how to recover from some common Git mistakes. - [Git for Computer Scientists](https://eagain.net/articles/git-for-computer-scientists/) is a short explanation of Git's data model, with less pseudocode and more fancy diagrams than these lecture notes. - [Git from the Bottom Up](https://jwiegley.github.io/git-from-the-bottom-up/) is a detailed explanation of Git's implementation details beyond just the data model, for the curious. - [How to explain git in simple words](https://smusamashah.github.io/blog/2017/10/14/explain-git-in-simple-words) * [git handbook](https://guides.github.com/introduction/git-handbook/),里面有一些资源 * [完整doc文档](https://git-scm.com/docs) * [resources to learn Git](https://try.github.io/) * [如何fork一个私库](https://stackoverflow.com/questions/10065526/github-how-to-make-a-fork-of-public-repository-private) * [tig](https://jonas.github.io/tig/doc/manual.html):图形化git历史 * >Tig是一个基于ncurses的git文本模式接口。它的功能主要是作为一个Git存储库浏览器,但也可以帮助在块级别上分段提交更改,并充当各种Git命令输出的分页器。 * 先[安装ncurses](https://blog.csdn.net/weixin_40123831/article/details/82490687) * [使用指南](https://www.jianshu.com/p/d9f60c0abbf7)