Workspace:工作区,Index / Stage:暂存区,Repository:仓库区(或本地仓库),Remote:远程仓库

远程仓库

安装,windows需要处理换行符

sudo yum install git

设置姓名和邮箱地址

git config --global user.name "Vane Hsiung"
git config --global user.email "1664548605@qq.com"

提高输出可读性

git config --global color.ui auto

设置文件

  • 显示当前的 Git 配置

    git config --list
    cat ~/.giconfig
    
  • 编辑Git配置文件

    git config -e [--global]
    

设置SSH,添加认证密码

ssh-keygen -t rsa -C "1664548605@qq.com"

添加公开密钥

将下面的密钥添加到 GitHub 设置中的 SSH key 中

cat ~/.ssh/id_rsa.pub

查看是否认证和通信成功

ssh -T git@github.com

获取远程仓库

clone 后默认在 master 分支下自动将 origin 设置为远程仓库标识符

git clone SSH

提速

git clone SSH --depth=1

加上 –depth 会只下载一个 commit,所以内容少了很多,速度也就上去了。

而且下载下来的内容是可以继续提交新的 commit、创建新的分支的。不影响后续开发,只是不能切换到历史 commit 和历史分支。

在一些场景下还是比较有用的:当需要切换到历史分支的时候也可以计算需要几个 commit,然后再指定 depth,这样也可以提高速度。

获取远程非master分支

-b 后是新建分支名称

git checkout -b branchName origin/branchName

获取指定分支

使用git拉代码时可以使用 -b 指定分支,拉取 develop 分支代码:

git clone -b develop http://gitslab.yiqing.com/declare/about.git

查看当前项目拉的是哪个分支的代码详情:

git branch -v

查看分支上的递交情况:

git show-branch

获取最新的远程仓库分支

远程仓库拉取代码并合并到本地,可简写为 git pull 等同于 git fetch && git merge

git pull <远程主机名> <远程分支名>:<本地分支名>

# 取回远程仓库的变化,并与本地分支合并
git pull origin branchName

# 使用rebase的模式进行合并
git pull --rebase <远程主机名> <远程分支名>:<本地分支名>

# 获取远程仓库特定分支的更新
git fetch <远程主机名> <分支名>

# 获取远程仓库所有分支的更新
git fetch --all

问题:For those who found this searching for an answer to fatal: 'origin/remote-branch-name' is not a commit and a branch 'local-branch-name' cannot be created from it, you may also want to try this first:

git fetch --all

git pull 不同的是 git fetch 操作仅仅只会拉取远程的更改,不会自动进行 merge 操作。对你当前的代码没有影响。

git rebase 让你的提交记录更加清晰可读

rebase 翻译为变基,他的作用和 merge 很相似,用于把一个分支的修改合并到当前分支上。

即逐个应用了 mater 分支的更改,然后以 master 分支最后的提交作为基点,再逐个应用 feature 的每个更改。

大部分情况下,rebase 的过程中会产生冲突的,此时,就需要手动解决冲突,然后使用依次 git add git rebase --continue 的方式来处理冲突,完成 rebase 的过程,如果不想要某次 rebase 的结果,那么需要使用 git rebase --skip 来跳过这次 rebase 操作。

git merge 和 git rebase 的区别

不同于 git rebase 的是,git merge 在不是 fast-forward(快速合并)的情况下,会产生一条额外的合并记录,类似 Merge branch 'xxx' into 'xxx' 的一条提交信息。

另外,在解决冲突的时候,用 merge 只需要解决一次冲突即可,简单粗暴,而用 rebase 的时候 ,需要依次解决每次的冲突,才可以提交。

同一台电脑配置多个 GItHub 账号

在日常使用 git 作为仓库使用的时候,有时可能会遇到这样的一些情况:

  1. 有两个 github 账号,一台电脑怎么同时连接这两个账号进行维护呢?
  2. 自己用一个 github 账号,平时用来更新自己的一些资料;公司使用的 gitlab(也是 git 的衍生产品)

如下是解决方案:

  • 创建默认 SSH Key

    ssh-keygen -t rsa -C "one@example.com"
    
  • 将公钥添加到 one@example.com 的 GitHub SSH key 中。

  • 测试 ssh key 是否成功

    ssh -T git@github.com
    
  • 如果设置过全局,则清除 git 的全局设置

    # 查看当前配置
    git config --list
    
    # 取消 global user.name user.email
    git config --global --unset user.name
    git config --global --unset user.email
    
  • 生成另外一个账号新的SSH keys

    ssh-keygen -t rsa -C "two@example.com"
    

    私钥需重命名,如 id_rsa_two。然后将对应的公钥添加到two@example.com的 Github SSH key 中。

  • 需添加新私钥到 SSH agent 中,因为默认只读取 id_rsa

    # Windows 在管理员下运行
    Get-Service ssh-agent
    Set-Service ssh-agent -StartupType Manual
    Start-Service ssh-agent
    
    # Linux
    eval `ssh-agent -s`
    
    # 添加私钥
    ssh-add ~/.ssh/id_rsa_new
    

    unable to start ssh-agent service

    Could not open a connection to your authentication agent

  • 配置 ~/.ssh/config 文件,用于配置私钥对应的服务器

    # Default github user(one@example.com)
    Host git@github.com
        HostName github.com
        User "Your GitHub Account Name"
        IdentityFile ~/.ssh/id_rsa
    
    # another user(two@example.com)
    # 建一个别名,新建的帐号使用这个别名做克隆和更新
    # "Host" 如果带了 "git@",如 "git@two.github.com",就会连接到 two.github.com
    # "Host" 没有带 "git@",就会正确的连接到 github.com
    Host two.github.com
        HostName github.com
        User "Your GitHub Account Name"
        IdentityFile ~/.ssh/id_rsa_two
    

    测试

    # default
    ssh -T git@github.com
    
    # another
    ssh -T git@two.github.com
    

    可能需要重启系统

    $ sudo systemctl reboot
    
  • 使用

    # default
    git remote add origin git@github.com:one/demo.git
    
    # another
    git remote add origin git@two.github.com:two/demo.git 
    
  • 设置每个项目的自己的 user.name 和 user.email

    git config  user.email "two@example.com"
    git config  user.name "two"
    

Git 中 HTTPS 和 SSH 的 Clone 方式区别

  • HTTPS:不管是谁,拿到url随便clone,但是在push的时候需要验证用户名和密码;
  • SSH:clone的项目你必须是拥有者或者管理员,而且需要在clone前添加SSH Key。SSH 在push的时候,是不需要输入用户名的,如果配置SSH key的时候设置了密码,则需要输入密码的。

本地仓库

初始化仓库

生成 .git 目录, 也就是当前目录的仓库,当前目录称为“附属于该仓库的工作树”

git init [project-name]

查看仓库的状态

git status        # 显示有变更的文件

向暂存区添加文件

  • 添加指定文件到暂存区

    git add [file1] [file2] ...
    
  • 添加指定目录到暂存区,包括子目录

    git add [dir]
    
  • 添加当前目录的所有文件到暂存区

    git add .
    
  • 删除工作区文件,并且将这次删除放入暂存区

    git rm [file1] [file2] ...
    
  • 改名文件,并且将这个改名放入暂存区

    git mv [file-original] [file-renamed]
    

原理(git add 为如下两步简写):

  • 为 example.txt 创建一个副本。git hash-object 命令把 example.txt 的当前内容压缩成二进制文件,称为一个 Git 对象,保存在 .git/objects 目录。并计算当前内容的哈希值,前 2 个字符作为目录名,后 38 个字符作为该对象的文件名

    git hash-object -w example.txt
    

    二进制对象里面会保存一些元数据,如果想看该文件原始的文本内容,需用git cat-file命令

    git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    
  • 所有变动的文件,Git 都记录在"暂存区",git update-index 命令用于在暂存区记录一个发生变动的文件

    git update-index --add --cacheinfo 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 example.txt
    

    git ls-files 命令显示暂存区当前内容

    git ls-files --stage
    

查看更改前后的差别

习惯:git commit 前先 git diff HEAD

git diff 默认查看工作树和暂存区的差别

HEAD查看工作树与最新提交的差别,HEAD 为指向当前分支中最新一次提交的指针,HEAD^ 指向 HEAD 的前一个提交,HEAD~6 则是 HEAD 之前的第6个提交。每一个分支都是一个文本文件,保存在 .git/refs/heads/ 目录中,文件的内容是最新提交的哈希值

git diff [HEAD]

将暂存区中文件保存到仓库历史记录中

-m 用于记录一行信息;不加 -m 记录详细信息,会新开编辑器进行编辑

  • 相当与 git add 与 git commit

    git commit -am "Message"    
    
  • 提交暂存区的指定文件到仓库区

     git commit [file1] [file2] ... -m "Message"
    
  • 提交工作区自上次commit之后的变化,直接到仓库区

    git commit -a
    
  • 提交时显示所有diff信息

    git commit -v
    

原理(git commit -m "first commit" 为如下两步简写):

  • git write-tree 命令保存当前的目录结构,生成一个 Git 对象

    git write-tree
    
  • git commit-tree 命令用目录结构 Git 对象生成一个 Git 对象,需添加提交说明,-p 参数用来指定父提交

    echo "first commit" | git e5a60f66d9966270c835343d4facc1c4bf44ed7a -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
    

修改提交信息

产生一个新的提交对象,替换掉上一次提交产生的提交对象

git commit --amend -m "Message"

重做上一次 commit,并包括指定文件的新变化

git commit --amend [file1] [file2] ...

压缩历史

用于拼错单词等简单的错误,选定当前分支中包含 HEAD(最新提交)在内的 number 个最新历史记录为对象并在编辑器中打开,pick 为合并对象,fixup 为被合并对象,最后 pick 提交信息会保留

git rebase -i HEAD~[number] 

查看提交日志

  • --pretty=short 用于只显示第一行简述信息

  • FileName 为文件名或目录名,只显示指定文件的日志

  • -p 用于显示文件的改动

  • --stat 显示 commit 历史,以及每次 commit 发生变更的文件

    git log [--pretty=short][FileName][-p][--stat]        # 显示当前分支的版本历史
    
  • 查看文件每次提交的diff

    git log -p FileName
    
  • 搜索提交历史,根据关键词

    git log -S [keyword]
    
  • 显示某个 commit 之后的所有变动,每个commit占据一行

    git log [tag] HEAD --pretty=format:%s
    
  • 显示某个 commit 之后的所有变动,其"提交说明"必须符合搜索条件

    git log [tag] HEAD --grep feature
    
  • 显示某个文件的版本历史,包括文件改名

    git log --follow [file]
    git whatchanged [file]
    

git log 的运行过程

  • 查找 HEAD 指针对应的分支
  • 找到分支的最新提交
  • 找到父节点(前一个提交)
  • 依此类推,显示当前分支的所有提交

查看当前仓库操作日志

git reflog

怎么查看当前的git分支是基于哪个分支创建的

git reflog --date=local | grep <branchname>

类似于如下

6b3db1f HEAD@{Fri Jul 9 16:05:23 2021}: checkout: moving from development to feature/api_xiongwen_dump

可知 feature/api_xiongwen_dump 基于 development

从暂存区撤销文件

停止追踪指定文件,但该文件会保留在工作区

git rm --cached [filename]

撤销提交

在当前提交后面,新增一次提交,抵消掉上一次提交导致的所有变化

git revert HEAD

想抵消多个提交,必须在命令行依次指定这些提交

git revert [倒数第一个提交] [倒数第二个提交]

回溯历史版本

  • 重置暂存区的指定文件,与上一次 commit 保持一致,但工作区不变

    git reset [file]
    
  • 重置暂存区与工作区,与上一次 commit 保持一致

    git reset --hard
    
  • 让最新提交的指针回到以前某个时点,该时点之后的提交都从历史中消失

    git reset 目标时间点哈希值        # 重置当前分支的指针为指定 commit,同时重置暂存区,但工作区不变
    
  • 默认情况下,git reset不改变工作区的文件(但会改变暂存区),--hard参数可以让工作区里面的文件也回到以前的状态

    git reset --hard 目标时间点哈希值        #  重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
    
  • 重置当前 HEAD 为指定 commit,但保持暂存区和工作区不变

    git reset --keep [commit]
    

添加远程仓库

origin 为远程仓库的标识符

git remote add origin SSH        # 增加一个新的远程仓库,并命名
git remote -v        # 显示所有远程仓库
git remote show [remote]        # 显示某个远程仓库的信息

推送至远程仓库

  • 推送的是当前分支
  • -u 在推送的同时将远程仓库的(origin仓库)的 branch 分支设为本地仓库当前分支的 upstream(上游) 运行 git pull 从远程仓库获取内容时,就可以省略参数
git push [-u origin branchName]        # 上传本地指定分支到远程仓库

git push --set-upstream origin [branchName]        # To push the current branch and set the remote as upstream

如果每次 git push 都需要输入账号和密码

  • 首先在 git 工作目录下:

    git config [--global] credential.helper store
    
  • 然后执行一次 git pull,这次输入账号和密码之后就不用再输入了。

Git push existing repo to a new and different remote repo server?

需求:从公司的账户 clone repo 到本地,添加注释,pull 到自己账户的私有 repo 中。

  1. Create a new repo at github.
  2. git remote rename origin upstream
  3. git remote add origin URL_TO_GITHUB_REPO
  4. git push origin master

Now you can work with it just like any other github repo. To pull in patches from upstream, simply run git pull upstream master && git push origin master.

分支

创建并切换分支

# 切换分支,并更新工作区
git checkout branchName    

#切换至上一分支
git checkout -            

# 新建一个分支,并切换到该分支
git checkout -b branchName        

# 新建本地分支,但不切换
git branch <branch-name> 

# 新建一个分支,指向指定commit
git branch [branch] [commit]        

# 新建一个分支,与指定的远程分支建立追踪关系
git branch --track [branch] [remote-branch]        

以图表形式查看分支

git log --graph

显示分支

# 列出所有本地分支
git branch    

# 列出所有远程分支
git branch -r        

# 同时显示本地仓库和远程仓库的分支信息
git branch -a    

# 用于创建分支
git branch branchName    

# 本地分支对应哪个远程分支
git branch -vv

合并分支

  • --no-ff 用于记录本次分支合并
  • 消除冲突:打开冲突的文件,在编辑器中改为想要的样子
git merge [--no-ff]  branchName        # 合并指定分支到当前分支
git cherry-pick [commit]        # 选择一个 commit,合并进当前分支

删除分支

  • 删除分支

    # 删除本地分支
    git branch -d [branch-name]
    
  • 删除远程分支

    git push origin --delete [branch-name]
    git branch -dr [remote/branch]
    

撤销工作区的文件修改

  • 先找暂存区,如果该文件有暂存的版本,则恢复该版本,否则恢复上一次提交的版本

    git checkout -- [filename]
    
  • 恢复某个 commit 的指定文件到暂存区和工作区

    git checkout [commit] [file]
    
  • 恢复暂存区的所有文件到工作区

    git checkout .
    

分支重命名

  • 重命名本地分支:

    • 在当前分支时

      git branch -m new_branch_name
      
    • 当不在当前分支时

      git branch -m old_branch_name new_branch_name
      
  • 重命名远端分支:

    假设是在当前分支,并且远端分支与本地分支名是一致的重命名本地分支

    git branch -m new_branch_name
    

    删除远程分支

    git push --delete origin old_branch_name
    

    上传新命名的本地分支

    git push origin new_branch_name
    

    关联修改后的本地分支与远程分支

    git branch --set-upstream-to origin/new_branch_name
    

标签

  • 列出所有 tag

    git tag
    
  • 新建一个 tag 在当前commit

    git tag [tag]
    
  • 新建一个tag在指定commit

    git tag [tag] [commit]
    
  • 删除本地 tag

    git tag -d [tag]
    
  • 删除远程 tag

    git push origin :refs/tags/[tagName]
    
  • 查看 tag 信息

    git show [tag]
    
  • 提交指定 tag

    git push [remote] [tag]
    
  • 提交所有tag

    git push [remote] --tags
    
  • 新建一个分支,指向某个tag

    git checkout -b [branch] [tag]
    

Git Ignore 规则

gitignore 规则:一种被 Git 忽略跟踪文件(或目录)的规则,这种规则对于已被跟踪的文件是无效的。当然它也提供配置自动加入跟踪的文件(或目录)的规则,但这种用法比较少。

.gitignore templates

规则

基本规则

  1. 空行或者以 # 开头的行都会被 Git 忽略。一般空行用于可读性的分隔, # 一般作注释用
  2. 以斜杠 / 结尾表示忽略目录
  3. / 开头表示根目录下的文件或目录
  4. 以叹号 ! 表示不忽略(跟踪)匹配到的文件或目录

示例如下:

# 本行为注释
.idea/          # 忽略仓库中所有.idea 目录
/.idea/         # 忽略仓库中根目录下的.idea 目录
/.settings      # 忽略仓库中根目录下的 .settings 文件或目录
~'$'*.docx      # office 打开时生成的临时文件

!etc/eclipse/.checkstyle    # 不忽略 .checkstyle 文件或目录

glob 模式规则

指 shell 使用的简化版正则表达式规则,其如下:

  1. 以星号 * 通配任意个字符(0-n)
  2. 以问号 ? 通配单个字符(1)
  3. 以方括号 [] 包含单个字符的匹配列表

示例如下:

debug?.logt         # 忽略 debug?.log 文件或目录,其中 ? 为任意一个字符

debug[0-9].log      # 忽略 debug0.log、debug2.log 等,但不忽略 debuga.log 文件
debug[01].log       # 仅忽略 debug0.log、debug1.log
debug[!01].log      # 不忽略 debug0.log、debug1.log

doc/*.txt           # 忽略 doc 目录下一级的所有以 .txt 结尾的文件或目录
doc/**/*.pdf        # 忽略 doc 目录下所有的 .pdf 文件或目录

注意

  1. 以上规则仅适用于未被缓存或加入版本控制的文件
  2. gitignore 支持 glob 模式
  3. gitignore 是从上到下一行一行匹配,后面的会覆盖前面的

用法

git 中提供 ignore 支持的方式据我了解共有三种,按照效果来分可分为 全局忽略_、_仓库忽略(远程共用)仓库忽略(本地使用)

全局忽略

本地所有仓库中共同使用的忽略规则。

可以通过命令行或直接修改文件的方式进行修改:

  • 命令行:直接修改 core.excludesFile 值即可

  • 修改配置文件:修改 ~/.gitconfig 文件,在 [core] 区域添加 excludesfile 属性,如下:

    [core]
    excludesFile = ~/.gitignore
    

注:

  • core.excludesFile 的默认值为 ~/.config/git/ignore

仓库忽略-远程

当前仓库使用,在当前仓库任意目录下创建 .gitignore 文件即可,Git 会通过它提供的规则忽略文件。

注:

  • 远程共用的效果需要将 .gitignore 文件加入 Git 的版本管理
  • .gitignore 文件规则存在冲突时,当前目录中的优先于父级目录中的

仓库忽略-本地

当前仓库使用。

修改 $GIT_DIR/info/exclude 文件,该文件规则和 .gitignore 文件一致。

注:

  • $GIT_DIR 为当前仓库中的 .git/ 目录

命令行式忽略

通过命令行提供忽略规则,根据参数不同,可达到 远程/本地 的效果。

各位如果观察过 $GIT_DIR/info/exclude 文件,可以发现该文件第一行有这么一个命令

git ls-files --others --exclude-from=.git/info/exclude

实际上通过此命令也可以设置忽略规则文件。

总结

以下为个人对各种忽略规则的一般准则吧,各位可以参考参看:

  1. 命令行形式的,通常不使用,毕竟有以下更简单方便的方式实现。
  2. .gitignore: 当前存储库中有开发人员都要忽略的跟踪文件
  3. $GIT_DIR/info/exclude:单个存储库个人忽略的配置文件,如个人为了更好 debug,开启日志配置等
  4. ~/.gitconfigcore.excludesFile 指定的文件:任意情况下用户希望忽略的文件,如IDE配置文件目录(当然 项目中也一般会添加此忽略规则)。

Pull Request

当你想更正别人仓库里的错误时,要走一个流程:

  1. 先 fork 别人的仓库,相当于拷贝一份,相信我,不会有人直接让你改修原仓库的。
  2. clone 到本地分支,做一些 bug fix。
  3. 发起 pull request 给原仓库,让他看到你修改的 bug。
  4. 原仓库 review 这个 bug,如果是正确的话,就会 merge 到他自己的项目中

至此,整个 pull request 的过程就结束了。

拉取请求,就是请求对方拉取我本地仓库的 bug fix,合并到对方的 repo 中。以对方的视角来看,我的本地仓库就是一个远程仓库。因为我们是在请求对方做什么,所以要以对方视角来看,即 pull,因为对方可能同意,也可能不同意,所以是请求,即 pull request。

GitHub Hosts

GitHub520 项目无需安装任何程序,通过修改本地 hosts 文件,试图解决:

  • GitHub 访问速度慢的问题
  • GitHub 项目中的图片显示不出的问题

花 5 分钟时间,让你"爱"上 GitHub。

$ sudo sh -c 'sed -i "/# GitHub520 Host Start/Q" /etc/hosts && curl https://raw.hellogithub.com/hosts >> /etc/hosts'

GitHub Pages

使用 GitHub

GitHub Pages 是一项静态站点托管服务,它直接从 GitHub 上的仓库获取 HTML、CSS 和 JavaScript 文件,(可选)通过构建过程运行文件,然后发布网站。

有三种类型的 GitHub Pages 站点:项目、用户和组织。 项目站点连接到 GitHub 上托管的特定项目。 用户和组织站点连接到特定的 GitHub 帐户。

To publish a user site, you must create a repository owned by your user account that’s named <username.github.io>. Repositories using the legacy <username.github.com> naming scheme will still be published, but visitors will be redirected from http(s)://<username.github.com> to http(s)://<username.github.io. If both a <username.github.com> and <username.github.io> repository exist, only the <username.github.io> repository will be published.

GitHub Pages sites are publicly available on the internet, even if the repository for the site is private or internal. 如果站点的仓库中有敏感数据,您可能想要在发布前删除它。

GitHub Pages 站点的发布来源是存储站点源文件的分支和文件夹。用户和组织站点的默认发布源是仓库默认分支的根目录。 项目站点的默认发布来源是 gh-pages 分支的根目录。

您可以创建自己的静态文件或使用静态站点生成器为您构建站点。默认情况下,GitHub Pages 将使用 Jekyll 来构建您的站点。

GitHub Pages 站点受到以下使用限制的约束:

  • GitHub Pages source repositories have a recommended limit of 1GB.
  • 发布的 GitHub Pages 站点不得超过 1 GB。
  • GitHub Pages sites have a soft bandwidth limit of 100GB per month.
  • GitHub Pages sites have a soft limit of 10 builds per hour.

可在 Repository 的 Settings 中配置 GitHub Pages 站点的发布源或取消发布 GitHub Pages 站点。

使用 jekyll

Github Docs 与 Jekyll 文档不一致,Windows 并未正式支持 Jekyll。

使用 Hexo

我选择 Hexo,一个是安装简单;一个是文档好。

GitHub Actions

GitHub Actions 是什么

持续集成由很多操作组成,比如自动抓取代码、运行测试、登录远程服务器、发布到第三方服务等。GitHub 把这些操作就称为 actions。

很多操作在不同项目里面是类似的,可以共享。GitHub 允许开发者把每个操作写成独立的脚本文件,存放到代码仓库,使得其他开发者可以引用。

可在官方市场awesome actions 找 action。

workflow 文件

GitHub Actions 的配置文件叫做 workflow 文件,存放在代码仓库的 .github/workflows 目录。

workflow 文件采用 YAML 格式,一个库可以有多个 workflow 文件。GitHub 发现 .github/workflows 目录里有 .yml 文件,就会自动运行该文件。

配置字段:

  • name:工作流程的名称。如果省略 name,GitHub 将其设置为相对于仓库根目录的工作流程文件路径

  • on:必要,触发工作流程的 GitHub 事件的名称

    on: [push, pull_request]
    
  • on.<push|pull_request>.<branches|tags>:您可以将工作流配置为在特定分支或标记上运行

    on:
      push:
        branches:    
          - main
          - 'mona/octocat'
          - 'releases/**'
        tags:        
          - v1
          - v1.* 
    
  • jobs:工作流程运行包括一项或多项作业。每项作业必须关联一个 ID

    • jobs.<job_id>.name:job_id 里面的 name 字段是任务的说明

      jobs:
        my_first_job:
          name: My first job
        my_second_job:
          name: My second job
      
    • jobs.<job_id>.needs:作业默认是并行运行。needs字段指定当前任务的运行顺序

      jobs:
        job1:
        job2:
          needs: job1
        job3:
          needs: [job1, job2]
      

      此例中作业执行顺序:job1、job2、job3

    • jobs.<job_id>.runs-on:必需,运行作业的机器类型

      runs-on: ubuntu-latest
      
  • jobs.<job_id>.steps:作业包含一系列任务,称为 steps

    • jobs.<job_id>.steps.name:步骤名称

    • jobs.<job_id>.steps.uses:引用的 Actions

      steps:    
        # Reference a specific commit
        - uses: actions/setup-node@74bc508
        # Reference a minor version of a release
        - uses: actions/setup-node@v1.2
        # Reference a branch
        - uses: actions/setup-node@main
      
    • jobs.<job_id>.steps.run:使用操作系统 shell 运行命令行程序

      - name: Clean install dependencies and build
        run: |
          npm ci
          npm run build    
      

参考

GitHub Actions 入门教程

GitHub Actions

GitHub Packages

Learn to safely publish and consume packages, store your packages alongside your code, and share your packages privately with your team or publicly with the open source community. You can also automate your packages with GitHub Actions.

持续集成(Continuous integration,简称CI)

概念

持续集成指的是,频繁地(一天多次)将代码合并(集成)到主干源码仓库。在 CI 中可以通过自动化等手段高频率地去获取产品反馈并响应反馈的过程。

流程

  • 提交:开发者向代码仓库提交代码
  • 测试(第一轮):代码仓库对提交的代码跑自动化测试
    • 单元测试:针对函数或模块的测试
    • 集成测试:针对整体产品的某个功能的测试,又称功能测试
    • 端对端测试:从用户界面直达数据库的全链路测试
  • 构建:将源码转换为可以运行的实际代码,会安装依赖,配置各种资源等。常用的构建工具如下
  • 测试(第二轮):第二轮是全面测试
  • 部署:直接部署
  • 回滚:当前版本发生问题,回滚到上一个版本的构建结果

Commit message

社区有多种 Commit Message Conventions。本文介绍 Angular 规范

格式化的 Commit message 好处

  • 提供更多的历史信息,方便浏览

    git log HEAD --pretty=format:%s
    
  • 可以过滤某些 commit,便于查找信息

    git log HEAD --grep feature
    
  • 可以直接从 commit 生成 Change Log

Commit message 的格式

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>
  • Header 只有一行

    • type 用于说明 commit 的类别
      • feat:新功能
      • fix:修补bug
      • docs:文档
      • style: 格式
      • refactor:重构
      • test:增加测试
      • chore:构建过程或辅助工具的变动
      • Revert:当前 commit 用于撤销以前的 commit
    • scope 用于说明 commit 影响的范围
    • subject 是 commit 目的的简短描述
      • 以动词开头,使用第一人称现在时
      • 第一个字母小写
      • 结尾不加句号
  • Body 部分是对本次 commit 的详细描述

  • Footer

    • 不兼容变动:如果当前代码与上一个版本不兼容,则以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动理由和迁移方法

    • 关闭 Issue:如果当前 commit 针对某个issue,那么可以在 Footer 部分关闭这个 issue

      Closes #123, #245, #992
      

Commitizen

Commitizen 是一个撰写 Commit message 的工具

  • Install the Commitizen cli tools

    npm install commitizen -g
    
  • Initialize your project to use the cz-conventional-changelog adapter

    commitizen init cz-conventional-changelog --save-dev --save-exact
    
  • 以后,凡是用到 git commit 命令,一律改为使用 git cz

参考

Commit message 和 Change log 编写指南

YAML(YAML Ain’t a Markup Language)

YAML 是专门用来写配置文件的语言

简介

规则:

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用 Tab 键,只允许使用空格。
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略
  • 对象和数组可以结合使用,形成复合结构

对象

一组键值对

animal: pets

行内表示法

hash: { name: Steve, foo: bar } 

数组

一组连词线开头的行

- Cat
- Dog
- Goldfish

数据结构的子成员是一个数组,则可以在该项下面缩进一个空格

-
 - Cat
 - Dog
 - Goldfish

行内表示法

animal: [Cat, Dog]

纯量

  • 字符串:字符串默认不使用引号表示

    str: 这是一行字符串
    

    包含空格或特殊字符,需要放在引号之中,单引号和双引号都可以使用,双引号不会对特殊字符转义

    str: '内容: 字符串'
    

    单引号之中如果还有单引号,必须连续使用两个单引号转义

    str: 'labor''s day' 
    

    字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格

    str: 这是一段
      多行
      字符串
    

    多行字符串可以使用|保留换行符,也可以使用>折叠换行

    this: |
      Foo
      Bar  
    that: >
      Foo
      Bar  
    

    +表示保留文字块末尾的换行,-表示删除字符串末尾的换行

    s1: |
        Foo
    
    s2: |+
        Foo
    

    s3: |- Foo

字符串之中可以插入 HTML 标记

```yaml
message: |

  <p style="color: red">
    段落
  </p>

参考

YAML 语言教程

The Official YAML Web Site

开源许可证

一般情况下,软件的源代码只由编写者拥有,而开源(即开放源代码,Open Source Code)是指一种更自由的软件发布模式。简单来说,开源软件的特点就是把软件程序和源代码文件一起打包提供给用户,让用户在不受限制地使用某个软件功能的基础上还可以对代码按需修改,让软件更贴合硬件环境,让功能更符合工作需求。用户还可以将其编制成衍生产品再发布出去。用户一般享有使用自由、复制自由、修改自由、创建衍生品自由,以及收费自由。是的,您没有看错,用户具备创建衍生品和收费的自由。这也就是说,可以对一个开源软件进行深度定制化加工。如果修改过的程序更加好用,或者颇具新的特色,只要符合原作者的许可要求,我们就完全可以合法地将软件进行商标注册,以商业版的形式再发布出去,只要有新用户使用您的软件并支付相应的费用,那就是您的收入。这也正好符合了黑客和极客对自由的追求,因此在合作与竞争中,国内外的开源社区慢慢生长出了强健的根基,人气也非常高。

但是,如果开源软件只单纯追求“自由”而牺牲了程序员的利益,这肯定会影响程序员的创作热情。为了平衡两者的关系,截至目前,世界上已经有100多种被开源促进组织(OSI,Open Source Initiative)确认的开源许可证,用于保护开源工作者的权益。对于那些只知道一味抄袭、篡改、破解或者盗版他人作品的不法之徒,终归会在某一天收到法院的传票。

考虑到大家没准儿以后会以开源工作者的身份编写出一款畅销软件,因此刘遄老师根据开源促进组织的推荐建议以及实际使用情况,为大家筛选出了程序员最喜欢的前6名的开源许可证,并教大家怎么从中进行选择。提前了解最热门的开源许可证,并在未来选择一个合适的可最大程度地保护自己软件权益的开源许可证,这对创业公司来讲能起到事半功倍的作用。

开源许可证总览:https://opensource.org/licenses/alphabetical

Tips:上述提到的“开源许可证”与“开源许可协议”的含义完全相同,只不过是英文翻译后两种不同的叫法,这里不作区别。

Tips:自由软件基金会(Free Software Foundation,FSF)是一个非营利组织,其使命是在全球范围内促进计算机用户的自由,捍卫所有软件用户的权利。

大家经常会在开源社区中看到Copyleft这个单词,这是一个由自由软件运动所发展出的概念,中文被翻译为“著佐权”或者“公共版权”。与Copyright截然相反,Copyleft不会限制使用者复制、修改或再发布软件。

此外,大家应该经常会听到别人说开源软件是free的,没错,开源软件就是自由的。这里的free千万不要翻译成“免费”,这样就大错特错了,这与您去酒吧看到的“第一杯免费”的意思可相差甚远。

下面我们来看一下程序员最喜欢的前6名的开源许可证,以及它们各自赋予用户的权利。

GPL

**GNU通用公共许可证(**General Public License,GPL):目前广泛使用的开源软件许可协议之一,用户享有运行、学习、共享和修改软件的自由。GPL最初是自由软件基金会创始人Richard Stallman起草的,其版本目前已经发展到了第3版。GPL的目的是保证程序员在开源社区中所做的工作对整个世界是有益的,所开发的软件也是自由的,并极力避免开源软件被私有化以及被无良软件公司所剥削。

现在,只要软件中包含了遵循GPL许可证的产品或代码,该软件就必须开源、免费,因此这个许可证并不适合商业收费软件。遵循该许可证的开源软件数量极其庞大,包括Linux内核在内的大多数的开源软件都是基于GPL许可证的。GPL赋予了用户著名的五大自由。

**使用自由:**允许用户根据需要自由使用这个软件。

**复制自由:**允许把软件复制到任何人的计算机中,并且不限制复制的数量。

**修改自由:**允许开发人员增加或删除软件的功能,但软件修改后必须依然基于GPL许可证。

**衍生自由:**允许用户深度定制化软件后,为软件注册自己的新商标,再发行衍生品的自由。

**收费自由:**允许在各种媒介上出售该软件,但必须提前让买家知道这个软件是可以免费获得的。因此,一般来讲,开源软件都是通过为用户提供有偿服务的形式来营利的。

LGPL

较宽松通用公共许可证(Lesser GPL, LGPL):一个主要为保护类库权益而设计的GPL开源协议。与标准GPL许可证相比,LGPL允许商业软件以类库引用的方式使用开源代码,而不用将其产品整体开源,因此普遍被商业软件用来引用类库代码。简单来说,就是针对使用了基于LGPL许可证的开源代码,在涉及这部分代码,以及修改过或者衍生出来的代码时,都必须继续采用LGPL协议,除此以外的其他代码则不强制要求。

如果您觉得LGPL许可证更多地是关注对类库文件的保护,而不是软件整体,那就对了。因为该许可证最早的名字是Library GPL,即GPL类库开源许可证,保护的对象有glibc、GTK widget toolkit等类库文件。

BSD

**伯克利软件发布版(**Berkeley Software Distribution, BSD)许可证:另一款被广泛使用的开源软件许可协议。相较于GPL许可证,BSD更加宽松,适合于商业用途。用户可以使用、修改和重新发布遵循该许可证的软件,并且可以将软件作为商业软件发布和销售,前提是需要满足下面3个条件。

如果再发布的软件中包含开源代码,则源代码必须继续遵循BSD许可证。

如果再发布的软件中只有二进制程序,则需要在相关文档或版权文件中声明原始代码遵循了BSD许可证。

不允许用原始软件的名字、作者名字或机构名称进行市场推广。

Apache

Apache许可证(Apache License):顾名思义,是由Apache软件基金会负责发布和维护的开源许可协议。作为当今世界上最大的开源基金会,Apache不仅因此协议而出名,还因市场占有率第一的Web服务器软件而享誉行业。目前使用最广泛的Apache许可证是2004年发行的2.0版本,它在为开发人员提供版权及专利许可的同时,还允许用户拥有修改代码及再发布的自由。该许可证非常适合用于商业软件,现在热门的Hadoop、Apache HTTP Server、MongoDB等项目都是基于该许可证研发的。程序开发人员在开发遵循该许可证的软件时,要严格遵守下面4个条件。

该软件及其衍生品必须继续使用Apache许可证。

如果修改了程序源代码,需要在文档中进行声明。

若软件是基于他人的源代码编写而成的,则需要保留原始代码的许可证、商标、专利声明及原作者声明的其他内容信息。

如果再发布的软件中有声明文件,则需在此文件中注明基于了Apache许可证及其他许可证。

MIT

MIT许可证(Massachusetts Institute of Technology License):源于麻省理工学院,又称为X11协议。MIT许可证是目前限制最少的开源许可证之一,用户可以使用、复制、修改、再发布软件,而且只要在修改后的软件源代码中保留原作者的许可信息即可,因此普遍被商业软件(例如jQuery与Node.js)所使用。也就是说,MIT许可证宽松到一个新境界,即用户只要在代码中声明了MIT许可证和版权信息,就可以去做任何事情,而无须承担任何责任。

MPL

**Mozilla公共许可证(**Mozilla Public License,MPL):于1998年初由Netscape公司的Mozilla小组设计,原因是它们认为GPL和BSD许可证不能很好地解决开发人员对源代码的需求和收益之间的平衡关系,因此便将这两个协议进行融合,形成了MPL。2012年年初,Mozilla基金会发布了MPL 2.0版本(目前为止也是最新的版本),后续被用在Firefox、Thunderbird等诸多产品上。最新版的MPL公共许可证有以下特点。

在使用基于MPL许可证的源代码时,后续只需要继续开源这部分特定代码即可,新研发的软件不用完全被该许可证控制。

开发人员可以将基于MPL、GPL、BSD等多种许可证的代码一起混合使用。

开发人员在发布新软件时,必须附带一个专门用于说明该程序的文件,内容要有原始代码的修改时间和修改方式。

总结

估计大家在看完上面琳琅满目的许可证后,会心生怨念:“这不都差不多吗?到底该选哪个呢?”写到这里时,刘遄老师也是一脸无助:“到底该怎么让大家进行选择呢?”搜肠刮肚之际突然眼前一亮,乌克兰程序员Paul Bagwell创作的一幅流程图正好对刚才讲过的这6款开源许可证进行了汇总归纳,具体如下图所示。

开源许可证的选择流程图

众所周知,绝大部分的开源软件在安装完毕之后即可使用,很难在软件界面中找到相关的收费信息。所以经常会有同学提问:“刘老师,开源社区的程序员总要吃饭的呀,他们是靠什么营利呢?”针对这个问题,网络上好像只有两种声音:

**情怀——**开源社区的程序员觉悟好,本领强,写代码纯粹是为了兴趣以及造福社会;

**服务——**先让用户把软件安装上,等用好、用习惯之后,再通过提供一些维护服务来营利。

这两种解释都各有道理,但是不够全面。读者也不要把开源软件和商业软件完全对立起来,因为好的项目也需要好的运营模式。就开源软件来讲,营利模式具体包括以下5种。

多条产品线:如MySQL数据库便有个人版和企业版两个版本,即个人版完全免费,起到了很好的推广作用;企业版则通过销售授权许可来营利。

技术服务型:JBoss应用服务器便是典型代表,JBoss软件可自由免费使用,软件提供方通过技术文档、培训课程以及定制开发服务来盈利。

软硬件结合:比如IBM公司在出售服务器时,一般会为用户捆绑销售AIX或Linux系统来确保硬件设施的营利。

技术出版物:比如O’Reilly既是一家开源公司,也是一家出版商,诸多优秀图书都是由O’Reilly出版的。

品牌和口碑:微软公司曾多次表示支持开源社区。大家对此可能会感到意外,但这是真的!Visual Studio Code、PowerShell、TypeScript等软件均已开源。大家是不是瞬间就对微软公司好感倍增了呢?买一份正版系统表示支持也就是人之常情了。

SSH 原理与运用

数字签名与数字证书

鲍勃有两把钥匙,一把是公钥,另一把是私钥。

鲍勃把公钥送给他的朋友们——帕蒂、道格、苏珊——每人一把。

苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密,就可以达到保密的效果。

鲍勃收信后,用私钥解密,就看到了信件内容。只要鲍勃的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。

鲍勃给苏珊回信,决定采用"数字签名"。他写完后先用Hash函数,生成信件的摘要(digest)。

然后,鲍勃使用私钥,对这个摘要加密,生成"数字签名"(signature)。

鲍勃将这个签名,附在信件下面,一起发给苏珊。

苏珊收信后,取下数字签名,用鲍勃的公钥解密,得到信件的摘要。由此证明,这封信确实是鲍勃发出的。

苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。此时,苏珊实际拥有的是道格的公钥,但是还以为这是鲍勃的公钥。因此,道格就可以冒充鲍勃,用自己的私钥做成"数字签名",写信给苏珊,让苏珊用假的鲍勃公钥进行解密。

后来,苏珊感觉不对劲,发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找"证书中心"(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成"数字证书"(Digital Certificate)。

鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。

苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明"数字签名"是否真的是鲍勃签的。

远程登录

  • 1995年,芬兰学者 Tatu Ylonen 设计了 SSH 协议,用于计算机之间的加密登录。本文针对的实现是 OpenSSH

  • 基本用法:

    • 假定你要以用户名 user,登录远程主机 host

      ssh user@host
      
    • 如果本地用户名与远程用户名一致,登录时可以省略用户名

      ssh host
      
    • SSH 的默认端口是 22,使用 p 参数修这个端口

      ssh -p 2222 user@host
      
  • 中间人攻击(Man-in-the-middle attack)

    • SSH 加密登录过程

      • 远程主机收到用户的登录请求,把自己的公钥发给用户。
      • 用户使用这个公钥,将登录密码加密后,发送给远程主机。
      • 远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
  • 中间人攻击:攻击者插在用户与远程主机之间,用伪造的公钥,获取用户的登录密码,再用这个密码登录远程主机。

  • 口令登录:第一次登录远程主机时,会询问是否接受远程主机公钥(是否继续连接),并显示公钥指纹——公钥长度较长(这里采用RSA算法,长达 1024 位),很难比对,所以对其进行MD5计算,将它变成一个 128 位的指纹。用户通过比对远程网站上贴出的公钥指纹,决定是否接受这个远程主机的公钥。当远程主机的公钥被接受以后,它就会被保存在文件 $HOME/.ssh/known_hosts 之中。

  • 公钥登录:省去口令登录每次都必须输入密码的步骤。用户将自己的公钥储存在远程主机上,登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来,远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。

    • ssh-keygen

      $HOME/.ssh/ 目录下生成两个文件:公钥 id_rsa.pub 和私钥 id_rsa

远程操作与端口转发

  • SSH 可以在用户和远程主机之间,建立命令和数据的传输通道

  • 绑定本地端口:让那些不加密的网络连接,全部改走 SSH 连接

    ssh -D 8080 user@host
    

    建立一个 socket,去监听本地的 8080 端口。一旦有数据传向那个端口,就自动把它转移到 SSH 连接上面,发往远程主机。

  • 本地端口转发:假定 host1 是本地主机,host2 是远程主机。由于种种原因,这两台主机之间无法连通。但是,另外还有一台 host3,可以同时连通前面两台主机。因此,很自然的想法就是,通过 host3,将 host1 连上 host2。

    ssh -L 2121:host2:21 host3
    

    L 参数接受三个值——“本地端口:目标主机:目标主机端口”。SSH 绑定本地端口 2121,指定 host3 将所有的数据转发到目标主机 host2 的 21 端口。本地端口转发使得 host1 和 host3 之间仿佛形成一个数据传输的秘密隧道,因此又被称为"SSH 隧道"。

  • 远程端口转发:host1 与 host2 之间无法连通,必须借助 host3 转发,而 host3 是一台内网机器,它可以连接外网的 host1,但是反过来就不行。解决办法是从 host3 上建立与 host1 的 SSH 连接,然后在 host1 上使用这条连接。在 host3 执行下面的命令

    ssh -R 2121:host2:21 host1
    

    R 参数也是接受三个值——“远程主机端口:目标主机:目标主机端口”。它让 host1 监听它自己的 2121 端口,然后将所有数据经由 host3,转发到 host2 的 21 端口。

Tip & Questions

Repository size limits for GitHub.com

Hard limits:

  • Individual files in a repository are strictly limited to a 100 MB maximum size limit.
  • Repositories have a hard size limit of 100GB.

解决SSH自动断线问题

在连接远程SSH服务的时候,经常会发生长时间后的断线,或者无响应(无法再键盘输入)。 总体来说有两个方法:

一、客户端定时发送心跳

putty、SecureCRT、XShell都有这个功能,设置请自行搜索

此外在Linux下:

  • 修改本机/etc/ssh/ssh_config

    # vim /etc/ssh/ssh_config
    
  • 添加

    ServerAliveInterval 30
    ServerAliveCountMax 100
    

    即每隔30秒,向服务器发出一次心跳。若超过100次请求,都没有发送成功,则会主动断开与服务器端的连接。

二、服务器端定时向客户端发送心跳(一劳永逸)

  • 修改服务器端 ssh配置 /etc/ssh/sshd_config

    # vim /etc/ssh/sshd_config
    
  • 添加

    ClientAliveInterval 30
    ClientAliveCountMax 6
    

    ClientAliveInterval表示每隔多少秒,服务器端向客户端发送心跳,是的,你没看错。

    下面的ClientAliveInterval表示上述多少次心跳无响应之后,会认为Client已经断开。

    所以,总共允许无响应的时间是60*3=180秒。

new mode 100755

出现

$ git diff filename
old mode 100644
new mode 100755 

但是文件内容并没有发生改变

产生这个问题的原因就是:filemode的变化,文件chmod后其文件某些位是改变了的,如果严格的比较原文件和chmod后的文件,两者是有区别的,但是源代码通常只关心文本内容,因此chmod产生的变化应该忽略,所以设置一下:

$ git config --add core.filemode false

Check if pull needed in Git

First use git remote update, to bring your remote refs up to date. Then you can do one of several things, such as:

  1. git status -uno will tell you whether the branch you are tracking is ahead, behind or has diverged. If it says nothing, the local and remote are the same.
  2. git show-branch *master will show you the commits in all of the branches whose names end in ‘master’ (eg master and origin/master).

If you use -v with git remote update (git remote -v update) you can see which branches got updated, so you don’t really need any further commands.

However, it looks like you want to do this in a script or program and end up with a true/false value. If so, there are ways to check the relationship between your current HEAD commit and the head of the branch you’re tracking, although since there are four possible outcomes you can’t reduce it to a yes/no answer. However, if you’re prepared to do a pull --rebase then you can treat “local is behind” and “local has diverged” as “need to pull”, and the other two (“local is ahead” and “same”) as “don’t need to pull”.

You can get the commit id of any ref using git rev-parse <ref>, so you can do this for master and origin/master and compare them. If they’re equal, the branches are the same. If they’re unequal, you want to know which is ahead of the other. Using git merge-base master origin/master will tell you the common ancestor of both branches, and if they haven’t diverged this will be the same as one or the other. If you get three different ids, the branches have diverged.

To do this properly, eg in a script, you need to be able to refer to the current branch, and the remote branch it’s tracking. The bash prompt-setting function in /etc/bash_completion.d has some useful code for getting branch names. However, you probably don’t actually need to get the names. Git has some neat shorthands for referring to branches and commits (as documented in git rev-parse --help). In particular, you can use @ for the current branch (assuming you’re not in a detached-head state) and @{u} for its upstream branch (eg origin/master). So git merge-base @ @{u} will return the (hash of the) commit at which the current branch and its upstream diverge and git rev-parse @ and git rev-parse @{u} will give you the hashes of the two tips. This can be summarized in the following script:

#!/bin/sh

UPSTREAM=${1:-'@{u}'}
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse "$UPSTREAM")
BASE=$(git merge-base @ "$UPSTREAM")

if [ $LOCAL = $REMOTE ]; then
    echo "Up-to-date"
elif [ $LOCAL = $BASE ]; then
    echo "Need to pull"
elif [ $REMOTE = $BASE ]; then
    echo "Need to push"
else
    echo "Diverged"
fi

Note: older versions of git didn’t allow @ on its own, so you may have to use @{0} instead.

The line UPSTREAM=${1:-'@{u}'} allows you optionally to pass an upstream branch explicitly, in case you want to check against a different remote branch than the one configured for the current branch. This would typically be of the form remotename/branchname. If no parameter is given, the value defaults to @{u}.

The script assumes that you’ve done a git fetch or git remote update first, to bring the tracking branches up to date. I didn’t build this into the script because it’s more flexible to be able to do the fetching and the comparing as separate operations, for example if you want to compare without fetching because you already fetched recently.

Setting the default editor for Git

Pick one:

  • Set core.editor in your Git config:

    git config --global core.editor "vim"
    
  • Set the GIT_EDITOR environment variable:

    export GIT_EDITOR=vim
    

解决代码提交的冲突

更新时间:2022-07-01 GMT+08:00

什么是代码提交冲突?

在多人团队使用代码托管服务时,不可避免的会出现两个人同时修改了一个文件的情况,这时在推送(push)代码到代码托管仓库时就会出现代码提交冲突并推送失败,如下图就是因为本地仓库与远程仓库文件修改的冲突所产生的推送失败。

img

说明:

  • 不同版本的Git、不同编译工具的Git插件所返回提示的内容不完全一致,但所表达的意思基本一致。

  • 只要在返回提示的内容中解读出,推送失败、另一个仓库成员,两个信息,一般即为产生了提交冲突。

  • Git在文件合并时是比较智能的,对于同一个文件不同位置的修改内容会自动合并,只有在同一个文件同一个位置被同时修改时(本地仓与远程仓的当前版本有差异),才会产生冲突。

  • 在分支合并时,有时也会产生冲突,这时的判定方式与解决办法与提交远程仓库时的冲突基本一样,如下图是本地分支branch1向master分支合并时产生了冲突(file01文件的修改冲突了)。

    img

如何解决代码提交冲突?

当代码提交冲突产生时,我们可以将远程代码仓库拉取(pull)到本地仓库的工作区,这时Git会将可以合并的修改内容进行合并,并将不能合并的文件内容进行提示,开发者只需要对提示的冲突内容进行修改即可再次推送到远程仓库(add → commit → push),这时冲突就解决完毕了。

如下图所示,在做拉取(pull)操作时,Git提示您,一个文件合并时产生了冲突。

img

当然在修改冲突文件时应该考虑清楚,必要时要与冲突方联系协商解决,避免覆盖他人代码。

**说明:**git pull可以理解为 git fetch 的操作 + git merge的操作,其详细说明如下:

git fetch origin master #从远程主机的master分支拉取最新内容   
git merge FETCH_HEAD    #将拉取下来的最新内容合并到当前所在的分支中 

在merge的时候,会将有冲突不能合并的内容做出提示。

示例:冲突的产生与解决

下面我们来模拟一个情景来帮助理解冲突的产生和解决的过程,情景如下。

某公司的一个项目使用代码托管服务和Git工具来管理,这个项目有一个功能(假设此功能涉及的修改文件是file01)由开发者1号(以下用01_dev表示)和开发者2号(以下用02_dev表示)共同开发,项目上线前一周,大家都在修改代码,产生了如下情况。

  1. file01存储在远程仓库,此时文件内容如下。

    img

  2. 01_dev在本地仓库修改了文件file01的第二行等内容,并已经成功推送到了远程仓库,此时01_dev的本地仓库和远程仓库的文件内容如下。

    img

  3. 此时02_dev也在本地仓库修改了文件file01的第二行等内容,在推送远程仓库时

    Git提示file01文件上产生了冲突

    了,02_dev的本地仓库文件内容如下,此时与远程仓库的冲突很明显。

    img

  4. 02_dev将远程仓库的代码拉取到本地,发现文件第二行开始的冲突并马上联系01_dev进行冲突的解决

  5. 打开冲突的文件(如下图所示),他们发现,都对第2行进行了修改,也都在最后一行添加了内容,Git将第二行开始的内容识别为冲突。

    img

    说明:

    • Git很智能的将两个人的修改同时显示出来,并用“=======”分割开来
      • “«««<HEAD” 与 “=======” 中间的是冲突位置中对应的本地仓库的修改。
      • “=======” 与 “»»»>” 中间的是冲突位置中对应的远程仓库的修改(也就是刚拉取下来的内容)。
      • “»»»>” 后面是本次的提交ID。
    • “«««<HEAD”、“=======”、“»»»>”、提交ID并非实际编写的代码,解决冲突时注意删除。
  6. 最后两人商量后认为最优的解决方案是将两个人的修改内容都保留,由02_dev负责修改,修改后02_dev的本地仓库文件内容如下图,同时保留了两个人的修改和新增内容。

    img

  7. 这样02_dev就可以重新推送(add → commit → push)本次合并后的更新到远程仓库,推送成功后,远程仓库文件内容如下。此时冲突解决

    img

**说明:**在上面的示例中,我们使用txt文本方式进行的演示,在实际开发中不同的文本编辑器、编程工具的Git插件中,对冲突的展示会略有不同。

如何避免冲突的产生?

代码提交、合并冲突经常发生,但只要在代码开发前,做好仓库预处理工作,就能有效的避免冲突的产生。

示例:冲突的产生与解决中,开发者02(02_dev)成功的解决了提交远程仓库时遇到的冲突问题,此时他的本地仓库与远程仓库的最新版本内容是一样的,但是开发者01(01_dev)本地仓库和远程仓库仍然是有版本差异的,此时如果直接推送本地仓库(push),仍然会产生冲突,那么如何避免呢?

方式一(推荐新手使用):

如果开发者本地的仓库不常更新使用,在做本地修改时,可以重新clone一份远程仓库的内容到本地,修改后再次提交,这样简单直接的解决了版本差异问题,但缺点是如果仓库较大、更新记录较多,clone过程将耗费一定的时间。

方式二(实际工作标准):

如果开发者每天都要对本地仓库进行修改,则建议在本地新建一条开发分支进行代码修改,在要提交远程仓库时,切换到master分支并将远程仓库的最新master分支内容拉取到本地,在本地进行分支合并,对产生的冲突进行修复,成功将内容合并到master分支后,再提交到远程仓库。

如何在代码托管服务的控制台上解决分支合并冲突?

代码托管服务支持云端的分支管理,当在进行分支合并时,有时也会产生冲突,下面我们来模拟一次产生了冲突的分支合并请求,并将其解决。

  1. 新建一个仓库

  2. 在仓库中新建一个文件,在本案例中,我们在master分支上新建一个名为file03的文件,其内容初始编写如下。

    img

  3. 基于master分支新建一个分支,在本案例中,我们将其命名为branch007。

    新建成功后,在master分支、branch007分支中的内容是一模一样的,下面我们需要让它们产生差异。

  4. 在master分支中,将file03的内容修改为如下图所示,将提交信息填写为“modify in master”。

    img

  5. 切换到branch007分支,并将file03文件修改为如下图所示,将提交信息填写为“modify in branch007”。此时切换分支即可直观的看到两个分支上已经产生了差异,也就是冲突。

    img

  6. 新建一个合并请求,选择将branch007分支合入到master分支,如下图单击**“确定”**按钮即可提交一条分支合并请求。

    此时将自动跳转到“合并请求详情”页面,当然您也可以在“合并请求列表”中单击请求的名称进入此页面,如下图所示,代码托管服务提示您当前的合并状态为**“冲突”,并建议您“在线解决冲突”“本地解决冲突”**

    img

  7. 下面我们根据提示,解决冲突:

    • 在线解决冲突(

      推荐在代码量较小或涉及冲突的代码量较小的情况下使用)

      1. 单击提示内容“在线解决冲突”,弹出如下图页面非常直观的展示了代码冲突。

        在此页面中我们可以直接选择**“应用我的”“应用他的”**来选择一方的修改作为最终修复后的内容。

        img

      2. 当情况较复杂,简单的直接覆盖无法解决问题时,可单击

        img

        进入“手动编辑”模式,如下图所示,可以看到跟示例:冲突的产生与解决

        中的冲突展现格式很像。

        img

      3. 在上述页面中手动修改代码以解决冲突,并进行提交即可。

        **注意:**提交时注意需要填写提交信息。

        上图中“««” 、“»»”、“====”等所在行是冲突展现与分割符,在修改代码解决冲突时,要注意将其删除。

    • 本地解决冲突

      (推荐在大型项目中使用)

      单击提示内容**“本地解决冲突”**,即弹出指导内容如下图所示,按照步骤操作即可。

      img

    **说明:**代码托管服务会根据您的分支名自动生成适合您的Git命令,您只需要复制并在本地仓库执行即可。

  8. 使用上述两种方法之一,解决了冲突之后,分支合并状态变为**“开启”,此时可以单击“合入”**按钮,进行分支合并的操作了,系统会提示您合并成功。

    (可选)当然您也可以使用分支合并评审流程

    此时master、branch007两个分支的内容是一样的了,您可以切换分支进行查看验证。