查看帮助

任何一个命令忘记或不太清楚怎么用时,都可查看帮助文档

  1. # 查看命令帮助
  2. git <command> -h
  3. # 打开浏览器查看帮助文档
  4. git <command> --help

特别说明

  1. 以下所有git命令 能修改的都是git跟踪的文件,所谓跟踪就是git的暂存区(index)存在这个文件
  2. 工作区新建的文件,不会被跟踪
  3. git add 命令会把新建的文件到暂存区,之后该文件的修改会被git自动记录

设置用户名和邮箱

  1. $ git config --global user.name "John Doe"
  2. $ git config --global user.email johndoe@example.com

本地仓库操作

  1. # 初始化一个仓库git-demo
  2. git init git-demo
  3. # 工作区新建文件readme.md
  4. echo "readme" >readme.md
  5. # 添加到暂存区
  6. git add readme.md
  7. #添加所有新建文件到暂存区
  8. git add .
  9. # 从暂存区删除
  10. git rm --cached readme.md
  11. 从暂存区删除了,工作区还保留,此时commit会删除本地仓库的文件,而工作区文件保留
  12. 开发工具生成的配置文件常常被错误提交到了本地仓库,可以这样删除,而本地配置文件保留不变
  13. # 强制删除文件 删除暂存区同时删除工作区文件
  14. git rm -f readme.md
  15. # 提交文件到本地仓库
  16. git commit -m 'readme'
  17. # 查看git提交记录
  18. git log
  19. # 修改readme.md文件
  20. # 查看差异
  21. git diff:比较工作区和缓存区的不同。
  22. git diff --cached:比较缓存区和仓库的不同。
  23. git diff 文件:比较指定文件在工作区和缓存区的不同。
  24. git diff 文件 --cached:比较指定文件在缓存区和仓库的不同
  25. # 还原提交
  26. 原上一次提交,具体做法是新增一次提交,抵消掉上一次提交导致的所有变化,
  27. 所以不用担心文件会被删除。还原多次提交写法如下:
  28. git revert [倒数第一个提交] [倒数第二个提交]
  29. git revert命令还有两个参数。
  30. --no-edit:执行时不打开默认编辑器,直接使用 Git 自动生成的提交信息。
  31. --no-commit:只抵消暂存区和工作区的文件变化,不产生新的提交。
  32. # 重置提交
  33. git reset [last good SHA]
  34. 重置某次提交,具体做法让最新提交的指针回到以前某个时点,
  35. 该时点之后的提交都从历史中消失。
  36. git reset --hard [last good SHA]
  37. git reset不改变工作区的文件(但会改变暂存区),
  38. --hard参数可以让工作区里面的文件也回到以前的状态
  39. 执行git reset命令之后,如果想找回那些丢弃掉的提交,
  40. 可以使用git reflog命令。不过,这种做法有时效性,
  41. 时间长了可能找不回来,而且只能找回你的reset
  42. # 替换提交
  43. git commit --amend -m "comment"
  44. 替换上一次commit,它的原理是产生一个新的提交对象,替换掉上一次提交产生的提交对象。

远程仓库

  1. # 添加远程仓库
  2. git remote add origin https://gitee.com/dakuohao/git-demo.git
  3. 默认远程仓库名称为 origin 默认分支master
  4. # 查看远程仓库
  5. git remote -v
  6. # 推送仓库
  7. git push origin master
  8. # 推送当前分支
  9. git push
  10. 默认为当前跟踪分支
  11. # 添加多个仓库
  12. git remote add demo1 https://gitee.com/dakuohao/git-demo1.git
  13. # 查看某个远程仓库
  14. git remote show origin
  15. # 重命名远程仓库
  16. git remote rename demo1 demo01
  17. # 删除远程仓库
  18. git remote remove demo01
  19. # 克隆一个远程仓库
  20. git clone https://gitee.com/dakuohao/git-demo.git
  21. # git支持各种协议,现在一般都推荐使用http/https或ssh协议,
  22. 当然还有本地协议和git协议(使用较少暂不演示)
  23. https://gitee.com/dakuohao/git-demo.git
  24. git@gitee.com:dakuohao/git-demo.git
  25. # 拉取分支
  26. # 有两种方式,一种是抓取 fetch,另一种是拉取 pull
  27. # fetch :从其他Git库的branch或tag或refs下载对象和引用到本地
  28. - 可以同时操作多个Git
  29. - 默认操作Gitorigin
  30. - 操作后更新.git/FETCH_HEAD
  31. 示例:
  32. git fetch,从默认远程库fetch所有内容
  33. git fetch origin,从origin远程库fetch所有内容
  34. # pull :从其他Git库的branch或tag或refs下载对象和引用,然后合并到当前分支
  35. # git pull等价于git fetch, git merge FETCH_HEAD
  36. # git pull --rebase等价于git fetch, git rebase FETCH_HEAD
  37. 示例:
  38. git pull,从默认远程库fetch所有内容,然后合并到当前分支
  39. git pull origin,从origin远程库fetch所有内容,然后合并到当前分支
  40. git pull origin remote_branch,从origin远程库的remote_branch分支fetch所有内容,然后合并到当前分支
  41. # 合并和变基 merge和rebase
  42. git mergegit rebase命令都是合并两个文件,
  43. 不同点在于产生的分支线不一样,
  44. git merge会保留两条提交分支线,merge后会增加一个新的merge commit
  45. git rebase只保留一条提交分支线,rebase会根据提交的先后顺序修改分支线,使其看起来更简洁
  46. 举个形象的例子:
  47. 假设您处于一般情况下-您已经在master分支上完成了一些工作,并且从origin分支中拉了出来,这也已经完成了一些工作。提取后,情况如下:
  48. - o - o - o - H - A - B - C (master)
  49. \
  50. P - Q - R (origin/master)
  51. 如果此时合并(git pull的默认行为),并且假设没有任何冲突,那么最终结果是:
  52. - o - o - o - H - A - B - C - X (master)
  53. \ /
  54. P - Q - R --- (origin/master)
  55. 另一方面,如果您进行了适当的变基,您将得到以下结果:
  56. - o - o - o - H - P - Q - R - A' - B' - C' (master)
  57. |
  58. (origin/master)
  59. 在这两种情况下,您的工作树的内容都应该相同。您刚刚创造了一段不同的历史。重新建立基础会重写您的历史记录,
  60. 使其看起来就像您已在原始的新主分支(R)上提交,而不是在原始提交的位置(H)上提交。
  61. 如果其他人已经从master分支中撤出,则永远不要使用rebase方法。
  62. 最后,请注意,您可以git pull通过将config参数设置branch.<name>.rebase为true
  63. 来实际设置给定分支使用rebase而不是merge 。
  64. 您也可以使用进行一次拉动git pull --rebase。

标签操作

tag的本质是一次commit的引用,对应文件夹.git/refs/tags

  1. 列出标签:git tag 这个命令以字母顺序列出标签,但是它们显示的顺序并不重要。
  2. 模糊查询某些标签: git tag -l "v1.8.5*"
  3. 创建附注标签:git tag -a <tag name> -m "comment"
  4. 创建轻量标签:git tag <tag name>
  5. 给以前的提交记录打标签:git tag -a <tag name> <校验和或部分校验和> 示例:git tag -a v1.2 9fceb02
  6. 推送标签用以共享:git push origin <tag name>
  7. 推送所有标签: git push origin --tags
  8. 删除标签:git tag -d <tagname>
  9. 检出标签并根据这个标签创建一个新的分支:git checkout -b <分支名> <标签名>

分支操作

  1. 分支分为本地分支和远端分支
  2. 查看本地所有分支:git branch
  3. 查看远程所有分支:git branch -r
  4. 查看所有分支:git branch -a
  5. 创建分支:git branch <branch name>
  6. 删除分支:git branch -d <branch name>
  7. 修改分支名:git branch -m old new
  8. 切换分支:git checkout <branch name>
  9. 创建新分支并切换到新分支:git checkout -b <branch name> 该命令是以下两条命令的简写git branch <branch name> git checkout <branch name>
  10. 合并分支:git merge <branch name> 这样会把某分支合并到当前分支
  11. 合并分支时如果有冲突,使用图形界面工具处理合并:git mergetool
  12. 推送分支 git push --set-upstream <remote> <branch>
  13. 推送本地所有分支:以下三种方式均可
  14. git push --all origin
  15. git push REMOTE --all
  16. git push REMOTE '*:*'
  17. 拉取全部远程仓库分支:
  18. 同步所有远程分支:git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done
  19. 抓取取所有分支变动:git fetch --all
  20. 拉取所有分支并合并: git pull --all
  21. 远程分支
  22. 查看所有远程分支:git branch -r
  23. 删除远程分支: git push origin --delete <remote name>

多分支组合成不同的工作流程git-flow

对Git分支的使用,可以变种出多种“工作流程”叫workflow或者git-flow
多分支开发非常灵活,这个真的可以玩出花来,不同的团队使用不同的分支工作流程,来管理他们项目文件,这里介绍几种世界上最常用的工作流程:

集中式工作流

跟使用svn一样的工作流,简单方便。
集中式系统中通常使用的是单点协作模型——集中式工作流。 一个中心集线器,或者说 仓库,可以接受代码,所有人将自己的工作与之同步。 若干个开发者则作为节点,即中心仓库的消费者与中心仓库同步。
Git常用操作 - 图1

这意味着如果两个开发者从中心仓库克隆代码下来,同时作了一些修改,那么只有第一个开发者可以顺利地把数据推送回共享服务器。 第二个开发者在推送修改之前,必须先将第一个人的工作合并进来,这样才不会覆盖第一个人的修改。 这和 Subversion (或任何 CVCS)中的概念一样,而且这个模式也可以很好地运用到 Git 中。
如果在公司或者团队中,你已经习惯了使用这种集中式工作流程,完全可以继续采用这种简单的模式。 只需要搭建好一个中心仓库,并给开发团队中的每个人推送数据的权限,就可以开展工作了。Git 不会让用户覆盖彼此的修改。
例如 John 和 Jessica 同时开始工作。 John 完成了他的修改并推送到服务器。 接着 Jessica 尝试提交她自己的修改,却遭到服务器拒绝。 她被告知她的修改正通过非快进式(non-fast-forward)的方式推送,只有将数据抓取下来并且合并后方能推送。 这种模式的工作流程的使用非常广泛,因为大多数人对其很熟悉也很习惯。
当然这并不局限于小团队。 利用 Git 的分支模型,通过同时在多个分支上工作的方式,即使是上百人的开发团队也可以很好地在单个项目上协作。

集成管理者工作流

github就是使用的这种工作流,特别适合分布在世界各地的开发者互相合作。
Git 允许多个远程仓库存在,使得这样一种工作流成为可能:每个开发者拥有自己仓库的写权限和其他所有人仓库的读权限。 这种情形下通常会有个代表“官方”项目的权威的仓库。 要为这个项目做贡献,你需要从该项目克隆出一个自己的公开仓库,然后将自己的修改推送上去。 接着你可以请求官方仓库的维护者拉取更新合并到主项目。 维护者可以将你的仓库作为远程仓库添加进来,在本地测试你的变更,将其合并入他们的分支并推送回官方仓库。 这一流程的工作方式如下所示:

  1. 项目维护者推送到主仓库。
  2. 贡献者克隆此仓库,做出修改。
  3. 贡献者将数据推送到自己的公开仓库。
  4. 贡献者给维护者发送邮件,请求拉取自己的更新。
  5. 维护者在自己本地的仓库中,将贡献者的仓库加为远程仓库并合并修改。
  6. 维护者将合并后的修改推送到主仓库。

Git常用操作 - 图2

这是 GitHub 和 GitLab 等集线器式(hub-based)工具最常用的工作流程。人们可以容易地将某个项目派生成为自己的公开仓库,向这个仓库推送自己的修改,并为每个人所见。 这么做最主要的优点之一是你可以持续地工作,而主仓库的维护者可以随时拉取你的修改。 贡献者不必等待维护者处理完提交的更新——每一方都可以按照自己的节奏工作。

主管与副主管工作流

适合超大规模项目开发(比如操作系统linux项目),很遗憾我没有参加过这么复杂的项目,暂不做评价。
这其实是多仓库工作流程的变种。 一般拥有数百位协作开发者的超大型项目才会用到这样的工作方式,例如著名的 Linux 内核项目。 被称为 副主管(lieutenant) 的各个集成管理者分别负责集成项目中的特定部分。 所有这些副主管头上还有一位称为 主管(dictator) 的总集成管理者负责统筹。 主管维护的仓库作为参考仓库,为所有协作者提供他们需要拉取的项目代码。 整个流程看起来是这样的:

  1. 普通开发者在自己的主题分支上工作,并根据 master 分支进行变基。 这里是主管推送的参考仓库的 master 分支。
  2. 副主管将普通开发者的主题分支合并到自己的 master 分支中。
  3. 主管将所有副主管的 master 分支并入自己的 master 分支中。
  4. 最后,主管将集成后的 master 分支推送到参考仓库中,以便所有其他开发者以此为基础进行变基。

Git常用操作 - 图3

这种工作流程并不常用,只有当项目极为庞杂,或者需要多级别管理时,才会体现出优势。 利用这种方式,项目总负责人(即主管)可以把大量分散的集成工作委托给不同的小组负责人分别处理,然后在不同时刻将大块的代码子集统筹起来,用于之后的整合。

常见git-flow

日常开发都是”功能驱动式开发”(Feature-driven development,简称FDD)。指的是,需求是开发的起点,先有需求再有功能分支(feature branch)或者补丁分支(hotfix branch)。完成开发后,该分支就合并到主分支,然后被删除。

  • Git flow
  • Github flow
  • Gitlab flow

    Git flow

    最早诞生、并得到广泛采用的一种工作流程,就是Git flow 。
    特点
    它最主要的特点有两个。
    Git常用操作 - 图4
    首先,项目存在两个长期分支。

  • 主分支master

  • 开发分支develop

前者用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版;后者用于日常开发,存放最新的开发版。
其次,项目存在三种短期分支。

  • 功能分支(feature branch)
  • 补丁分支(hotfix branch)
  • 预发分支(release branch)

一旦完成开发,它们就会被合并进develop或master,然后被删除。
Git flow 的详细介绍,请阅读我翻译的中文版《Git 分支管理策略》。
评价
Git flow的优点是清晰可控,缺点是相对复杂,需要同时维护两个长期分支。大多数工具都将master当作默认分支,可是开发是在develop分支进行的,这导致经常要切换分支,非常烦人。
更大问题在于,这个模式是基于”版本发布”的,目标是一段时间以后产出一个新版本。但是,很多网站项目是”持续发布”,代码一有变动,就部署一次。这时,master分支和develop分支的差别不大,没必要维护两个长期分支。

Github flow

Github flow 是Git flow的简化版,专门配合”持续发布”。它是 Github.com 使用的工作流程。

特点
它只有一个长期分支,就是master,因此用起来非常简单。
官方推荐的流程如下。
Git常用操作 - 图5

  • 第一步:根据需求,从master拉出新分支,不区分功能分支或补丁分支。
  • 第二步:新分支开发完成后,或者需要讨论的时候,就向master发起一个pull request(简称PR)。
  • 第三步:Pull Request既是一个通知,让别人注意到你的请求,又是一种对话机制,大家一起评审和讨论你的代码。对话过程中,你还可以不断提交代码。
  • 第四步:你的Pull Request被接受,合并进master,重新部署后,原来你拉出来的那个分支就被删除。(先部署再合并也可。)

评价
Github flow 的最大优点就是简单,对于”持续发布”的产品,可以说是最合适的流程。
问题在于它的假设:master分支的更新与产品的发布是一致的。也就是说,master分支的最新代码,默认就是当前的线上代码。
可是,有些时候并非如此,代码合并进入master分支,并不代表它就能立刻发布。比如,苹果商店的APP提交审核以后,等一段时间才能上架。这时,如果还有新的代码提交,master分支就会与刚发布的版本不一致。另一个例子是,有些公司有发布窗口,只有指定时间才能发布,这也会导致线上版本落后于master分支。
上面这种情况,只有master一个主分支就不够用了。通常,你不得不在master分支以外,另外新建一个production分支跟踪线上版本。

Gitlab flow

Gitlab flow 是 Git flow 与 Github flow 的综合。它吸取了两者的优点,既有适应不同开发环境的弹性,又有单一主分支的简单和便利。它是 Gitlab.com 推荐的做法。
上游优先
Gitlab flow 的最大原则叫做”上游优先”(upsteam first),即只存在一个主分支master,它是所有其他分支的”上游”。只有上游分支采纳的代码变化,才能应用到其他分支。
Chromium项目就是一个例子,它明确规定,上游分支依次为:

  • Linus Torvalds的分支
  • 子系统(比如netdev)的分支
  • 设备厂商(比如三星)的分支

持续发布
Gitlab flow 分成两种情况,适应不同的开发流程。
Git常用操作 - 图6
对于”持续发布”的项目,它建议在master分支以外,再建立不同的环境分支。比如,”开发环境”的分支是master,”预发环境”的分支是pre-production,”生产环境”的分支是production。
开发分支是预发分支的”上游”,预发分支又是生产分支的”上游”。代码的变化,必须由”上游”向”下游”发展。比如,生产环境出现了bug,这时就要新建一个功能分支,先把它合并到master,确认没有问题,再cherry-pick到pre-production,这一步也没有问题,才进入production。
只有紧急情况,才允许跳过上游,直接合并到下游分支。
版本发布
对于”版本发布”的项目,建议的做法是每一个稳定版本,都要从master分支拉出一个分支,比如2-3-stable、2-4-stable等等。
以后,只有修补bug,才允许将代码合并到这些分支,并且此时要更新小版本号。

一些小技巧

Pull Request

Git常用操作 - 图7
功能分支合并进master分支,必须通过Pull Request(Gitlab里面叫做 Merge Request)。
Git常用操作 - 图8
前面说过,Pull Request本质是一种对话机制,你可以在提交的时候,@相关人员或团队,引起他们的注意。

Protected branch

master分支应该受到保护,不是每个人都可以修改这个分支,以及拥有审批 Pull Request 的权力。
Github 和 Gitlab 都提供”保护分支”(Protected branch)这个功能。

Issue

Issue 用于 Bug追踪和需求管理。建议先新建 Issue,再新建对应的功能分支。功能分支总是为了解决一个或多个 Issue。
功能分支的名称,可以与issue的名字保持一致,并且以issue的编号起首,比如”15-require-a-password-to-change-it”。
Git常用操作 - 图9
开发完成后,在提交说明里面,可以写上”fixes #14”或者”closes #67”。Github规定,只要commit message里面有下面这些动词 + 编号,就会关闭对应的issue。

  • close
  • closes
  • closed
  • fix
  • fixes
  • fixed
  • resolve
  • resolves
  • resolved

这种方式还可以一次关闭多个issue,或者关闭其他代码库的issue,格式是username/repository#issue_number
Pull Request被接受以后,issue关闭,原始分支就应该删除。如果以后该issue重新打开,新分支可以复用原来的名字。

Merge节点

Git有两种合并:一种是”直进式合并”(fast forward),不生成单独的合并节点;另一种是”非直进式合并”(none fast-forword),会生成单独节点。
前者不利于保持commit信息的清晰,也不利于以后的回滚,建议总是采用后者(即使用—no-ff参数)。只要发生合并,就要有一个单独的合并节点。

Squash 多个commit

为了便于他人阅读你的提交,也便于cherry-pick或撤销代码变化,在发起Pull Request之前,应该把多个commit合并成一个。(前提是,该分支只有你一个人开发,且没有跟master合并过。)
Git常用操作 - 图10
这可以采用rebase命令附带的squash操作。