git修改远程仓库地址

问:Coding远程仓库地址变了,本地git仓库地址如何更新为最新地址

git修改远程仓库地址

方法有三种:

1、修改命令

git remote origin set-url [url]

2、先删后加

git remote rm origin
git remote add origin [url]

3、直接修改config文件

git 远程仓库管理

要参与任何一个 Git 项目的协作,必须要了解该如何管理远程仓库.远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有些可以写.同他人协作开发某 个项目时,需要管理这些远程仓库,以便推送或拉取数据,分享各自的工作进展.管理远程仓库的工作,包括添加远程库,移除废弃的远程库,管理各式远程库分 支,定义是否跟踪这些分支,等等.本节我们将详细讨论远程库的管理和使用.

查看当前的远程库

要查看当前配置有哪些远程仓库,可以用 git remote 命令,它会列出每个远程库的简短名字.在克隆完某个项目后,至少可以看到一个名为 origin 的远程库,Git 默认使用这个名字来标识你所克隆的原始仓库:

  $ git clone git://github.com/schacon/ticgit.git

  Initialized empty Git repository in /private/tmp/ticgit/.git/

  remote: Counting objects: 595, done.

  remote: Compressing objects: 100% (269/269), done.

  remote: Total 595 (delta 255), reused 589 (delta 253)

  Receiving objects: 100% (595/595), 73.31 KiB | 1 KiB/s, done.

  Resolving deltas: 100% (255/255), done.

  $ cd ticgit

  $ git remote

  
origin也可以加上 -v 选项(译注:此为 ?verbose 的简写,取首字母),显示对应的克隆地址:

$ git remote -v

origin git://github.com/schacon/ticgit.git如果有多个远程仓库,此命令将全部列出.比如在我的 Grit 项目中,可以看到:

  $ cd grit

  $ git remote -v

  bakkdoor git://github.com/bakkdoor/grit.git

  cho45 git://github.com/cho45/grit.git

  defunkt git://github.com/defunkt/grit.git

  koke git://github.com/koke/grit.git

origin [email protected]:mojombo/grit.git这样一来,我就可以非常轻松地从这些用户的仓库中,拉取他们的提交到本地.请注意,上面列出的地址只有 origin 用的是 SSH URL 链接,所以也只有这个仓库我能推送数据上去(我们会在第四章解释原因).

添加远程仓库

要添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用,运行 git remote add [shortname] [url]:

  $ git remote

  origin

  $ git remote add pb git://github.com/paulboone/ticgit.git

  $ git remote -v

  origin git://github.com/schacon/ticgit.git

pb git://github.com/paulboone/ticgit.git现在可以用字串 pb 指代对应的仓库地址了.比如说,要抓取所有 Paul 有的,但本地仓库没有的信息,可以运行 git fetch pb:

  $ git fetch pb

  remote: Counting objects: 58, done.

  remote: Compressing objects: 100% (41/41), done.

  remote: Total 44 (delta 24), reused 1 (delta 0)

  Unpacking objects: 100% (44/44), done.

  From git://github.com/paulboone/ticgit

  * [new branch] master -> pb/master

  • [new branch] ticgit -> pb/ticgit现在,Paul 的主干分支(master)已经完全可以在本地访问了,对应的名字是 pb/master,你可以将它合并到自己的某个分支,或者切换到这个分支,看看有些什么有趣的更新.

从远程仓库抓取数据

正如之前所看到的,可以用下面的命令从远程仓库抓取数据到本地:

$ git fetch [remote-name]此命令会到远程仓库中拉取所有你本地仓库中还没有的数据.运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个 分支合并到本地,或者只是取出某个分支,一探究竟.(我们会在第三章详细讨论关于分支的概念和操作.)

如果是克隆了一个仓库,此命令会自动将远程仓库归于 origin 名下.所以,git fetch origin 会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新(或是上次 fetch 以来别人提交的更新).有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并.(说 明:事先需要创建好远程的仓库,然后执行:git remote add [仓库名] [仓库url],git fetch [远程仓库名],即可抓取到远程仓库数据到本地,再用git merge remotes/[仓库名]/master就可以将远程仓库merge到本地当前branch.这种分支方式比较适合独立-整合开发,即各自开发测试好后 再整合在一起.比如,Android的Framework和AP开发.

可以使用–bare 选项运行git init 来设定一个空仓库,这会初始化一个不包含工作目录的仓库.

  $ cd /opt/git

  $ mkdir project.git

  $ cd project.git

$ git –bare init这时,Join,Josie 或者Jessica 就可以把它加为远程仓库,推送一个分支,从而把第一个版本的工程上传到仓库里了.)

如果设置了某个分支用于跟踪某个远端仓库的分支(参见下节及第三章的内容),可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支.在日常工作中我们经常这么用,既快且好.实际上,默认情况下 git clone 命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支(假设远程仓库确实有 master 分支).所以一般我们运行 git pull,目的都是要从原始克隆的远端仓库中抓取数据后,合并到工作目录中当前分支.

推送数据到远程仓库

项目进行到一个阶段,要同别人分享目前的成果,可以将本地仓库中的数据推送到远程仓库.实现这个任务的命令很简单: git push [remote-name] [branch-name].如果要把本地的 master 分支推送到 origin 服务器上(再次说明下,克隆操作会自动使用默认的 master 和 origin 名字),可以运行下面的命令:

$ git push origin master只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务.如果在你推数据前,已经有其他人推送了若干更新,那 你的推送操作就会被驳回.你必须先把他们的更新抓取到本地,并到自己的项目中,然后才可以再次推送.有关推送数据到远程仓库的详细内容见第三章.

查看远程仓库信息

我们可以通过命令 git remote show [remote-name] 查看某个远程仓库的详细信息,比如要看所克隆的origin 仓库,可以运行:

  $ git remote show origin

  * remote origin

  URL: git://github.com/schacon/ticgit.git

  Remote branch merged with 'git pull' while on branch master

  master

  Tracked remote branches

  master

  
ticgit除了对应的克隆地址外,它还给出了许多额外的信息.它友善地告诉你如果是在 master 分支,就可以用git pull 命令抓取数据合并到本地.另外还列出了所有处于跟踪状态中的远端分支.

实际使用过程中,git remote show 给出的信息可能会像这样:

  $ git remote show origin

  * remote origin

  URL: [email protected]:defunkt/github.git

  Remote branch merged with 'git pull' while on branch issues

  issues

  Remote branch merged with 'git pull' while on branch master

  master

  New remote branches (next fetch will store in remotes/origin)

  caching

  Stale tracking branches (use 'git remote prune')

  libwalker

  walker2

  Tracked remote branches

  acl

  apiv2

  dashboard2

  issues

  master

  postgres

  Local branch pushed with 'git push'

  
master:master它告诉我们,运行 git push 时缺省推送的分支是什么(译注:最后两行).它还显示了有哪些远端分支还没有同步 到本地(译注:第六行的 caching 分支),哪些已同步到本地的远端分支在远端服务器上已被删除(译注:Stale tracking branches 下面的两个分支),以及运行 git pull 时将自动合并哪些分支(译注:前四行中列出的 issues 和 master 分支).(此命令也可以查看到本地分支和远程仓库分支的对应关系.)

远程仓库的删除和重命名

在新版 Git 中可以用 git remote rename 命令修改某个远程仓库的简短名称,比如想把 pb 改成 paul,可以这么运行:

  $ git remote rename pb paul

  $ git remote

  origin

paul注意,对远程仓库的重命名,也会使对应的分支名称发生变化,原来的 pb/master 分支现在成了paul/master.

碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,可以运行 git remote rm 命令:

  $ git remote rm paul

  $ git remote

  origin

从撤销 rebase 谈谈 git 原理

假设我们有两个分支,a 和 b,它们的提交都有一个相同的父提交(master 指向的那次提交)。如图所示:

未分类

现在我们在分支 b 上,然后 rabase 到分支 a 上。如图所示:

未分类

平时开发中经常遇到这种情况,假设分支 a 和 b 是两个独立的 feature 分支,但是不小心被我们错误的 rebase 了。现在相当于两个 feature 分支中原本独立的业务被揉起来了,当然是我们不想看到的结果,那么如何撤销呢?

一种方案是利用 reflog 命令。

利用 reflog 撤销变基

我们先不考虑原理,直接上解决方案,首先输入 git reflog,你会看到如下图所示的日志:

未分类

最后的输出其实是最早的操作,我们逐条分析下:

  • HEAD@{8}: 这里我们创建了初始的提交
  • HEAD@{7}:检出了分支 a
  • HEAD@{6}:在分支 a 上做了一次提交,注意 master 分支没有变动
  • HEAD@{5}:从分支 a 回到分支 master,相当于向后退了一次
  • HEAD@{4}:检出了分支 b
  • HEAD@{3}:在分支 b 上做了一次提交,注意 master 分支没有变动
  • HEAD@{2}:这一步开始变基到分支 a,首先切换到分支 a 上
  • HEAD@{1}:把分支 b 对应的那次提交变基到分支 a 上
  • HEAD@{0}:变基结束,因为是在 b 上发起的变基,所以最后还切回分支 b

如果我们想撤销此次 rebase,只要输入以下命令就可以了:

git reset --hard HEAD@{3}

此时再看,已经“恢复”到 rebase 前的状态了。的是不是感觉很神奇呢,先别着急,后面会介绍这么做的原理。

git 工作原理简介

为了搞懂 git 是如何工作的,以及这些命令背后的原理,我想有必要对 git 的模型有基础的了解。

首先,每一个 git 目录都有一个名为 .git 的隐藏目录,关于 git 的一切都存储于这个目录里面(全局配置除外)。这个目录里面有一些子目录和文件,文件其实不重要,都是一些配置信息,后面会介绍其中的 HEAD 文件。子目录有以下几个:

  • info:这个目录不重要,里面有一个 exclude 文件和 .gitignore 文件的作用相似,区别是这个文件不会被纳入版本控制,所以可以做一些个人配置。

  • hooks:这个目录很容易理解, 主要用来放一些 git 钩子,在指定任务触发前后做一些自定义的配置,这是另外一个单独的话题,本文不会具体介绍。

  • objects:用于存放所有 git 中的对象,下面单独介绍。

  • logs:用于记录各个分支的移动情况,下面单独介绍。

  • refs:用于记录所有的引用,下面单独介绍。

本文主要会介绍后面三个文件夹的作用。

git 对象

git 是面向对象的!
git 是面向对象的!
git 是面向对象的!

没错,git 是面向对象的,而且很多东西都是对象。我举个简单的例子,来帮助大家理解这个概念。假设我们在一个空仓库里,编辑了 2 个文件,然后提交。此时都会有那些对象呢?
首先会有两个数据对象,每个文件都对应一个数据对象。当文件被修改时,即使是新增了一个字母,也会生成一个新的数据对象。
其次,会有一个树对象用来维护一系列的数据对象,叫树对象的原因是它持有的不仅可以是数据对象,还可以是另一个树对象。比如某次提交了两个文件和一个文件夹,那么树对象里面就有三个对象,两个是数据对象,文件夹则用另一个树对象表示。这样递归下去就可以表示任意层次的文件了。
最后则是提交对象,每个提交对象都有一个树对象,用来表示某一次提交所涉及的文件。除此以外,每一个提交还有自己的父提交,指向上一次提交的对象。当然,提交对象还会包含提交时间、提交者姓名、邮箱等辅助信息,就不多说了。
假设我们只有一个分支,以上知识点就足够解释 git 的提交历史是如何计算的了。它并不存储完整的提交历史,而是通过父提交的对象不断向前查找,得出完整的历史。
注意开头那张图片,分支 b 指向的提交是 9cbb015,不妨来看下它是何方神圣:

git cat-file -t 9cbb015
git cat-file -p 9cbb015

这里我们使用 cat-file 命令,其中 -t 参数打印对象的类型,-p 参数会智能识别类型,并打印其中的内容。输出结果如图所示:

未分类

可见 9cbb015 是一个提交对象,里面包含了树对象、父提交对象和各种配置信息。我们可以再打印树对象看看:

未分类

这表示本次提交只修改了 begin 这个文件,并且输出了 begin 这个文件对于的数据对象。

git 引用

既然 git 是面向对象的,那么有没有指正呢?还真是有的,分支和标签都是指向提交对象的指针。这一点可以验证:

cat .git/refs/heads/a

所有的本地分支都存储在 git/refs/heads 目录下,每一个分支对应一个文件,文件的内容如图所示:

未分类

可见,4a3a88d 刚好是本文第一张图中分支 a 所指向的提交。

我们已经搞明白了 git 分支的秘密,现在有了所有分支的记录,又有了每次提交的父提交对象,就能够得出像 SourceTree 或者文章开头第一张图那样的提交状态了。

至于标签,它其实也是一种引用,可以理解为不能移动的分支。只能永远指向某个固定的提交。

最后一个比较特殊的引用是 HEAD,它可以理解为指针的指针,为了证明这一点,我们看看 .git/HEAD 文件:

未分类

它的内容记录了当前指向哪个分支,refs/heads/b 其实是一个文件,这个文件的内容是分支 b 指向的那个提交对象。理解这一点非常重要,否则你会无法理解 checkout 和 reset 的区别。
这两个命令都会改变 HEAD 的指向,区别是 checkout 不改变 HEAD 指向的分支的指向,而 reset 会。举个例子, 在分支 b 上执行以下两个命令都会让 HEAD 指向 4a3a88d 这次提交(分支 a 指向的提交):

git checkout a
git reset --hard a

但 checkout 仅改变 HEAD 的指向,不会改变分支 b 的指向。而 reset 不仅会改变 HEAD 的指向,还因为 HEAD 指向分支 b,就把 b 也指向 4a3a88d 这次提交。

git 日志

在 .git/logs 目录中,有一个文件夹和一个 HEAD 文件,每当 HEAD 引用改变了指向的位置,就会在 .git/logs/HEAD 中添加了一个记录。而 .git/logs/refs/heads 这个目录中则有多个文件,每个文件对应一个分支,记录了这个分支 的指向位置发生改变的情况。
当我们执行 git reflog 的时候,其实就是读取了 .git/logs/HEAD 这个文件。

未分类

撤销 rebase 的原理

首先我们要排除一个误区,那就是 git 会维护每次提交的提交对象、树对象和数据对象,但并不会维护每次提交时,各个分支的指向。在介绍分支的那一节中我们已经看到,分支仅仅是一个保留了提交对象的文件而已,并不记录历史信息。即使在上一节中,我们知道分支的变化信息会被记录下来,但也不会和某个提交对象绑定。

也就是说,git 中并不存在某次提交时的分支快照

那么我们是如何通过 reset 来撤销 rebase 的呢,这里还要澄清另一个事实。前文曾经说过,某个时刻下你通过 SourceTree 或者 git log 看到的分支状态,其实是由所有分支的列表、每个分支所指向的提交,和每个提交的父提交共同绘制出来的。

首先 git/refs/heads 下的文件告诉我们有多少分支,每个文件的内容告诉我们这个分支指向那个提交,有了这个提交不断向前追溯就绘制出了这个分支的提交历史。所有分子的提交历史也就组成了我们看到的状态。
但我们要明确:不是所有提交对象都能看到的,举个例子如果我们把某个分支向前移一次提交,那个分支的提交线就会少一个节点,如果没有别的提交线包含这个节点,这个节点就看不到了。

所以在 rebase 完成后,我们以为看到了下面这样的提交线:

df0f2c5(master) --- 4a3a88d(a) --- 9cbb015(b)

实际上是这样的:

df0f2c5(master) --- 4a3a88d(a) --- 9d0618e(b)
   |
9cbb015

master 分支上依然有分叉,原来 9cbb015 这次提交依然存在,只不过没有分支的提交线包含它,所以无法看到而已。但是通过 reflog,我们可以找回 HEAD 头的每一次移动,所以能看到这次提交。

当我们执行这个命令时:

git reset --hard HEAD@{3}

再看一次 reflog 的输出:

未分类

HEAD@{3} 其实是它左侧 9cbb015 这次提交的缩写,所以上述命令等价于:

git reset --hard 9cbb015

前文说过,reset 不仅会移动 HEAD,还会移动 HEAD 所指向的分支,所以这个命令的执行结果就是让 HEAD 和分支 b 同时指向 9cbb015 这个提交,看起来像是撤销了 rebase。

但别忘了,分支 a 的上面还是有一次提交的,9d0618e 这次提交仅仅是没有分支指向它,所以不显示而已。但它真实的存在着,严格意义上来说,我们并没有真正的撤销此次 rebase。

Git如何回滚代码?

摘要: 多年以后,你面对一个需要回滚的Git仓库,准会想起这篇博客。

未分类

某一天,用户跟我反馈,他不能分配任务了。我去看了一下Fundebug捕获的报错信息:

未分类

可知,出错原因是前端发送的请求参数有问题。这个悲伤的故事是这样的:后端同时修改了多个接口,但是前端没有及时修改对应的接口调用。

这个问题不难解决,回滚代码就好了,但是,Git如何回滚代码呢?我花了点时间研究了一下。

测试仓库:Fundebug/git-rollback

https://github.com/Fundebug/git-rollback

我写了一个非常简单的测试仓库,master分支只有3个commit,每个commit只是添加一个文件,因此一共有3个文件:01.txt, 02.txt, 03.txt。

git log
commit b39476b9c730d312266fe9bc7f63e795f9ba856f
Author: kiwenlau <[email protected]>
Date:   Thu Jan 18 14:58:06 2018 +0800

    3个文件

commit 3821210392184432de18b596cee58ab7924e39f9
Author: kiwenlau <[email protected]>
Date:   Thu Jan 18 14:57:38 2018 +0800

    2个文件

commit 55d3012564e94a39f6686c0e532c0459ddc41ec4
Author: kiwenlau <[email protected]>
Date:   Thu Jan 18 14:56:41 2018 +0800

    1个文件

本文介绍的所有回滚代码的命令,都会在一个新的分支执行,方便大家理解:

未分类

git revert : 撤销特定commit

Git的每一个commit都对应着某些代码改动,那万一改错了呢?这时可以使用git reset来撤销某一次commit的改动。所谓撤销,就是把修改了的代码再改回来。

示例1

最简单的一个场景,就是撤销最近的1次commit:

git revert HEAD

最近1次commit是新增03.txt,撤销这个commit之后,test01分支就只剩下2个文件了:01.txt, 02.txt。

示例2

有时,可能需要撤销某个特定commit,比如,当我需要撤销第2个commit时,指定对应ID即可:

git revert 3821210392184432de18b596cee58ab7924e39f9

最近1次commit是新增02.txt,撤销这个commit之后,test02分支就只剩下2个文件了:01.txt, 03.txt。

由这个示例可知,使用git revert撤销commit时,仅仅针对所撤销的commit,与任何其他commit无关。

示例3

假设我们再做某个功能时,进行了多次commit,需要全部撤销。我们当然可以一个个commit依次revert,这样比较麻烦,其实可以一次性搞定:

git revert b39476b9c730d312266fe9bc7f63e795f9ba856f 3821210392184432de18b596cee58ab7924e39f9

撤销了后面2个commit,test03分支就只剩下1个文件了:01.txt。

示例4

示例3中所撤销的2个commit是连续的,其实,因为revert操作只与单个commit相关,我们也可以撤销多个非连续的commit:

git revert b39476b9c730d312266fe9bc7f63e795f9ba856f  55d3012564e94a39f6686c0e532c0459ddc41ec4

撤销了第1个和第3个commit,test04分支就只剩下1个文件了:02.txt。

git reset:重置为特定commit

示例5

在示例3中,撤销了两个连续的commit,这样做等价于将代码重置为第1个commit的状态:仅有01.txt这1个文件。使用git revert命令有些麻烦,我们可以直接使用git reset命令来实现:

git reset –hard 55d3012564e94a39f6686c0e532c0459ddc41ec4
将仓库强制重置为第1个commit,这样test05分支就只剩下1个文件了:01.txt。git reset –hard与git revert效果看起来一样,但是不同点在于,前者直接删除了后面2个commit及其代码,没有保存commi历史,因此这个操作是不可逆的!使用时应该特别小心。

如果你的本地仓库与远程仓库在reset之前是同步过的,reset之后,两者的commit不一致,本地仓库的commit落后与远程仓库,这样会导致push失败:

git push
To [email protected]:Fundebug/git-rollback.git
 ! [rejected]        test05 -> test05 (non-fast-forward)
error: failed to push some refs to '[email protected]:Fundebug/git-rollback.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

这是需要使用–force选项,这样远程仓库会被强制覆盖:

git push --force

示例6

reset时使用–hard选项是一个很危险的操作,因为它把commit以及代码全删了,没法恢复。如果你只希望删除commit,而保留修改过的代码的话,可以使用–soft选项。

git reset --soft 55d3012564e94a39f6686c0e532c0459ddc41ec4

这时,后面2个commit也被删除了,当前commit为第1个commit。但是,02.txt与03.txt并没有被删除。

git status
On branch test06
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   02.txt
    new file:   03.txt

这就意味着,当你的commit的代码有一些小问题时,可以重置commit,修改一下代码,如何重新commit即可。

参考

  • https://git-scm.com/docs/git-revert

  • https://blog.fundebug.com/2018/01/24/git-rollback-tutorial/

场景式解读 Git 工作流

未分类

很开心跟大家分享我自己对工作流的理解,包括我们公司自身正在进行的实践,还有 Coding 的一些用户提出的他们对于工作流的理解。
我希望在今天讲座之后在座的各位可以根据自身开发的具体工作内容,团队的规模和开发的形式,从我提出的几种工作流中找到自己合适的套路,也可以在自有的套路上扩展支撑自己公司特定的流程。

工作流

什么叫好的工作流呢?适合你的场景就是好,所以今天聊得是场景式解读工作流。工作流从字面意义上理解可以涵盖你工作的各个方面,包括任务管理产品最终上线的反馈,但是我们这边讲工作流只是用 Git 管理代码中,你与别人代码仓库如何协作的工作流。

未分类

这张图可以简单的看什么是 Git 工作流,有开发分支,有主干分支,主干分支上有一些发布,这是一个比较简略的工作流。
我从协作人员数量和开发周期这两个维度划分四个象限出来,划分了几类比较常见的场景。我将详细说明这四种场景下,如何进行工作流的选择。

未分类

工作人员数量比较少的,开发周期比较短的往往是小型外包项目。比如可能是小型外包公司,三五个人的团队,有前端和后端,项目可能是三个月,更快的是两个月搞定,没有后续维护的压力,这个项目交付之后基本告一段落,我想说的是项目即便不是外包项目,但若符合协作人员数量少,开发的周期短也适合这种工作流。

此外还有协作人员数量多,开发周期比较短。比如一个大型外包项目。项目往往有各种模块系统的集成,整个开发团队达到几十个人的规模,不同的人在为各个模块做开发工作,属于图示中第二种工作场景。

也有些项目,协作人员数量比较少,开发周期比较长。比如开源项目,你开发一个开源软件,定期可能要发布一些版本,偶尔还要接受一些其他人发过来的贡献。

最后一类是最复杂、最麻烦的,但却是很多互联网公司的真实使用场景,维护一整套线上运行的系统,但是这套系统没有开发终点,只是不停的在迭代,整个服务庞大,功能模块众多,开发人员的并行性也比较强。
大致上我认为解决中小型团队工作流的场景就分为这四种,我们来分别阐述一下适合他们的工作流程。

小型外包项目

未分类

小型外包项目大家往往都是三五个人在一起工作,沟通成本低,效率高,Code Review 需求不强,很多人说 Code Review 需求强不强不是项目决定的,但是小型外包项目具体的目的是尽快的帮客户把代码交付,即便我极力推荐 Code Review,然而在显示中这个过程事实上在大多数的外包公司并不存在。

未分类

小型外包项目的工作流其实非常的简单,一条主干就足够了,三五个人在一条分支上开发,你的代码写完提交上来,别人同步下来然后再提交,当某一个版本合适发布的时候发给客户验证,有问题的话再去修复再发一个版本,非常简单。如果是这种形式的项目,一条分支解决所有问题,不要让复杂的工作流变成你工作的绊脚石。

小型开源项目

未分类

小型开源项目往往是由个人或者小团队维护,特点是往往要接受第三方的贡献,Code Review 的需求强烈,在开源社区内 Code Review 是比较重要的过程;还有一个特点是开源项目往往有维护多个版本的需求,与小型外包项目不太一样,小型外包项目你跟客户交付的软件很少有 1.0 继续维护,2.0 继续维护的概念。

未分类

小型开源项目其实它的流程相对也是比较简单,可以看到这边其实有一个分支(红色的线),下面有紫色的点是主的开发分支。主开发分支上往往是 Master,主开发分支上会有很多提交,团队的成员来提交,包括第三方的爱好者看到你的代码觉得不错,从这边 Fork 得到一个自己的项目,做完一些贡献之后提交 Pull Request 到主干分支这里来,在这个过程中可以进行 Code Review,而最终发布的过程是由开源项目的维护者来决定的。开源项目也有可能维护多个版本的情况,维护多个版本就是多条分支线(红色)。

大型外包项目

未分类

大型外包项目的特点是开发人员比较多、分模块并行开发,Code Review 的需求强烈,此外需要比较短的时间内把这么大规模的项目做出来,分模块进行开发,并行开发也是必然的。

未分类

我们看一下大型外包项目比较适合用什么样的形式。
这个工作流从本质上来讲是基于特性分支来做的工作流,大型外包项目往往也没有多个大版本,一般情况下只有一个主版本,这条黑色的线就是主分支,主分支上可能有多个团队,多个团队负责不同的模块开发,某个团队基于主干开一个特性分支,提交完毕之后会发一个 MR 到主干分支来,在这个过程中可以做 Code Review,在这个过程中可以使用 Coding 的工具列出这个分支相对于主干分支的差异,评审通过后就可以合并到主干上去。其他的团队也可能会在不同的 Master 上的节点新建分支来进行开发。

并性开发的情况下,如果两个小组开发的内容是属于同一个模块,他们两个产生冲突的可能性非常大。如果说他们开发的功能是相对独立的,比如说这个是模块A 这个是模块B,这个人专门做账户系统,这边的人做订单的系统,那么他们两个冲突概率比较小。本质上讲,冲突的根本原因在于并行开发同一个文件,跟用什么版本控制工具和用什么分支管理模型都没有关系。并行开发的情况下避免冲突的点不在于一定要把这些分支做成一致,而是尽量的让这些部门和分支上开发的内容在项目层面上拆成不同的模块,通过项目模块划分方式减少冲突。
并行开发这种模型一条主干分支开若干个特性分支,特性分支经过评审合并到主干,主干还是会像小型外包项目一样由项目经理决定什么时间何时发布打个标签交给客户。

大型互联网项目

未分类

大型互联网项目是比较麻烦的,往往开发人员多,还会有一些紧急的情况。比如有一天你的用户突然告诉你的网站会泄露密码,我们需要立即快速的做线上修复。这跟传统外包项目不太一样,传统外包项目就算开发过程中已知现在项目的代码有 Bug,但是因为这个版本还没有交付给客户,不用一定要让团队停下工作立即将问题修复之后才能继续。

互联网项目往往 Code Review 的需求强烈,它是从源码的角度上,就是在提交的时候尽可能的避免线上问题的出现性。

未分类

图上黑色的一条线是 Master 分支,主分支上有很多提交的点,某一个团队可能基于某个点上进行开发,像刚才的大型外包公司类似,开一个分支,开发某一个功能,某个功能开发完毕的时候提交到 MR。

但这个过程会被线上出现紧急问题打断,我们需要基于线上的版本立即开一个新的分支,做一个快速的修复再合并到主干分支上去(hotfix)。

主干分支的每一个节点都代表一次线上的成功发布,主干分支最新的点代表现在线上运行的最新的版本。这就带来一个好处:主干分支上的点可以用来作为开发的基准。因为所有开发者都是基于主干分支做开发,这就要求保证主干分支上的代码必须是稳定可靠的,否则容易出现 A 在 B 的不可靠的代码基础上做了开发,如果最终验证 B 的代码有问题,需要回退的时候,往往会牵连到 A 的正常代码,这无疑是一种效率的降低。

而什么样的代码是“稳定可靠的代码”呢?当然大家的评判标准不同:

可能是:高级程序员写的代码
可能是:经过评审的代码
可能是:经过测试的代码
而最可靠的是:经过评审,测试,并最终经过生产环境验证的代码。

从这个角度考虑,我们提高“稳定可靠的代码”的审核标准,让主干分支最新版本始终与线上生产环境运行版本对应就可以避免掉这类的效率降低,同时因为更严苛的代码并入主干的标准使得线上的生产环境极大的减少因为代码 bug 导致的生产事故。

那某个分支基于比较老旧的版本开发,它的功能还没有开发完毕,而线上的版本有其他的团队开发,已经更新上线,导致这个东西落后怎么办呢?有两种办法:一种是 Rebase,Base 就是基准,Rebase 就是变更基准,图示有两个 Feature2,第二个 Feature2 就是 Rebase 之后的。另一种是 Merge ,Merge 是从主干分支往特性分支做合并,跟常规的 Merge Request 是反的,合并之后即可实现同步到所有主干上的代码,就不再落后了。所以两种方式解决你的特性分支落后于主干的情况,具体的情况可能与可根据不同的团队偏好调整。

Git 的工作原理

关于 Git 的原理,很多人听了分支、合并不知道怎么回事,很多人用了很久也不知道 Git 到底怎么存储文件。

未分类

这张图里可以看到有一条红色的线,这条红色的线是提交的历史,它对应于每个开发修订版列表,也对应于某一个主干分支。这条线的最新版本是 Master 和 v1.0 所指向的版本,v1.0 是一个标签,Master 是个分支,他们其实都指向一个具体的修订版(绿色的原点),你改一次代码、提交一次会产生一个新的修订版,这些分支会指向不同的修订版,这些指向是会变的。

我们都知道 Git 所管理的是代码库,而代码库就是一个文件夹,文件夹有很多代码和文件夹,文件夹有子文件夹,子文件夹中又有文件,Git 的每一个修订版都会对应一个 Tree,目录里面有很多子目录,子文件。

Git 的原理很简单,每一个修订版对应一个 Tree,每一个 Tree 又包含了所有版本的文件。

小贴士

冲突

我经常会收到一些用户和朋友的疑问,比如出现了冲突报错。冲突是什么呢?该如何解决呢?
先讲如何解决,解决冲突需要你决定这个冲突端内容最终应该是什么样的形式,解决完冲突的这个文件就是是你想要的最终结果,冲突解决是以业务为目的让原文件呈现最终状态的过程。

虽然冲突解决有一套理论,但是我们更希望从最初避免问题。在并行开发中,不同的开发者之间修改了同一个文件和同一个模块,就有可能产生冲突,一个东西被两个人改了,Git 就不知道该怎么处理。所以冲突的避免不是你使用什么流程模型、也不是使用什么代码管理工具解决,而是由你们的工作安排来解决的,避免的方式是让代码的结构划分的更合理,让不同的模块更为独立。

大文件

大型文件往往会严重拖慢 Git 的性能,Git 的性能消耗不光存在于代码托管的服务器端,也体现在用户端。主要影响是计算文件的 sha1 哈希和压缩,大型文件这两个操作都很慢。Git 本质上是用来管理原代码而不是管理大文件的,如果真的是想把大文件存在 Git 中推荐使用 Git LFS。

Git Bisect

都说 Git 是用来存储原代码仓库的可以帮你追溯历史,但很多人不知道该怎么干。例如说忽然出现一个 Bug,查遍所有资料,还是无法定位问题,那如何找到导致问题代码呢?Bisect 会让你快速的找到到底是哪一个提交导致的这个问题,你只需要告诉 Git 到底这个版本是好是坏就可以了。

具体点来讲是开始 Bisect 后,Git 会自动检出一个版本,你告诉他这个版本有没有这个bug,不管有还是没有,Git 都会在最小的可能范围中基于二分查找法给出下一个验证的提交,直至最终找到导致问题的提交。

Git Work-tree

有时候我们需要同时查看一个仓库内不同版本的文件,或者有需要同时编译多个不同版本的源代码。这时候直接使用 git checkout 是很麻烦的,必须不断的把版本来回切换,而使用 work-tree 可以很方便的给一份 git 仓库设置多个检出目录树(工作区)。

希望大家可以根据自己团队的实际情况及使用场景,找到适合自己的工作流。

在阿里云的CENTOS上搭建GIT服务器

最近突然想把一些原来在本机的代码用git管理起来,本来想再windows上搭一个服务器,转而一想反正有一个阿里云的centos,干脆用起来吧,于是说干就干。

一、检查服务器版本是否自带git

[root@~]# rpm -qa git
git-1.8.3.1-6.el7_2.1.x86_64
language-shell hljs

如果现实git-版本号这种说明已经安装过了,如果没有使用下面的命令安装

yum install git 
language-shell hljs

二、创建git用户并设置密码

[root@]# useradd my2017
[root@]# passwd my2017
Changing password for user my2017.
New password: 
Retype new password: 
passwd: all authentication tokens updated successfully.

三、生成ssh公钥

[root@]# su my2017
[my2017@]$ cd ~
[my2017@ ~]$ ls
[my2017@ ~]$ pws
bash: pws: command not found
[my2017@ ~]$ pwd
/home/my2017
[my2017@ ~]$ mkdir .ssh
[my2017@ ~]$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/my2017/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/my2017/.ssh/id_rsa.
Your public key has been saved in /home/my2017/.ssh/id_rsa.pub.
The key fingerprint is:
0e:b0:b2:b2:4e:8d:a6:08:12:ea:cb:55:6b:f5:64:e2 my2017@iZ2ze3jauhy9b4gdjn!2s2w1
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|                 |
|    .            |
|     o           |
|. . ...oSo       |
|..oo. +o=        |
|=+.o o E..       |
|Xo. .            |
|==.              |
+-----------------+
[my2017@ ~]$ cd .ssh/
[my2017@ .ssh]$ cat id_rsa.pub >> ~/.ssh/authorized_keys
[my2017@ .ssh]$ exit
exit

四、创建git仓库

[root@ gitrepository]# mkdir mygit.git
[root@ gitrepository]# cd mygit.git/
[root@ mygit.git]# git --bare init
Initialized empty Git repository in /data/gitrepository/mygit.git/

五、修改权限

[root@ data]# chown -R my2017:my2017 gitrepository/
[root@ data]# cd gitrepository/
[root@ gitrepository]# ls -al
total 12
drwxr-xr-x 3 my2017 my2017 4096 Oct 29 23:14 .
drwxr-xr-x 6 root      root      4096 Oct 29 22:54 ..
drwxr-xr-x 7 my2017 my2017 4096 Oct 29 23:15 mygit.git
[root@ gitrepository]# cd mygit.git/
[root@ mygit.git]# ls -al
total 40
drwxr-xr-x 7 my2017 my2017 4096 Oct 29 23:15 .
drwxr-xr-x 3 my2017 my2017 4096 Oct 29 23:14 ..
drwxr-xr-x 2 my2017 my2017 4096 Oct 29 23:15 branches
-rw-r--r-- 1 my2017 my2017   66 Oct 29 23:15 config
-rw-r--r-- 1 my2017 my2017   73 Oct 29 23:15 description
-rw-r--r-- 1 my2017 my2017   23 Oct 29 23:15 HEAD
drwxr-xr-x 2 my2017 my2017 4096 Oct 29 23:15 hooks
drwxr-xr-x 2 my2017 my2017 4096 Oct 29 23:15 info
drwxr-xr-x 4 my2017 my2017 4096 Oct 29 23:15 objects
drwxr-xr-x 4 my2017 my2017 4096 Oct 29 23:15 refs

配置SSH key

六、本地测试

安装git.exe

https://git-for-windows.github.io/

有时候不好下,多刷新几遍。

安装 TortoiseGit

https://tortoisegit.org/

我的一贯宗旨是能鼠标点的绝逼不敲代码。。。。

tortoisegit报错提示:

Disconnected:No supported authentication methods available
(server sent:publickey,gssapi-keyex,gssapi-with-mic)

直接用git客户端报错:

Warning: Permanently added (ECDSA) to the list of known hosts.
my@: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
fatal: Could not read from remote repository.

解决方法:

禁止git用户登录

禁用git用户shell登陆

出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。

vim /etc/passwd

找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。

Git鉴权方式SSH key和用户名密码

Git的鉴权方式分为 ssh 和 https,ssh 需要使用 ssh 秘钥,而https需要使用用户名和密码。有时候会每次提交都需要输入用户名和密码,下面介绍相关的Windows平台Git鉴权操作。

概述

使用Git之前需要安装 Git-bash 并且把它加到环境变量,git-bash下载地址(https://git-scm.com/downloads)。

如果使用的是 git-desktop,安装的时候会自带 git-bash,需要配置到环境变量,它的地址一般在:

C:Users用户名AppDataLocalGitHubPortableGit,

在GitHub文件夹下搜索 git.exe 可以快速找到位置。一般是Portable_XXX文件夹下的cmd目录下面,有 git.exe, git-gui.exe。

在 git-bash 终端,使用 cd 命令可以跳转目录,注意这儿跳转目录的格式为:

$ cd /g/Projects/sdk_plugin

其中盘符为 G ,对于交互界面,可以直接拖动文件夹到 git-bash 中,会自动转成相应的文件夹路径。

对于某个项目,git clone 之后,在当前项目会拉取源代码,注意默认隐藏的 .git 文件夹,该文件夹下面有 config 文件,里面有关于该项目的配置。

另外需要配置项目的用户名和邮箱,这会用作提交代码的用户名和邮箱记录。

$ git config --global user.name "uusama"
$ git config --global user.email "[email protected]"

关于鉴权方式

git clone 的地址有下面两种

  • https: https://github.com/用户名/GitProject.git
  • ssh: [email protected]:用户名/GitProject.git

如果是 gitlab 内网服务端,这对应的 https 开头的地址或者 git 开头的地址。

第一种方式为 http 方式,需要提供用户名和密码,第二种方式是 ssh 方式,需要配置 ssh 的 RSA 秘钥。

可以通过修改项目的git配置文件: .git/config 中的url部分,从而改变鉴权方式。

[remote "origin"]
    url = https://github.com/youyouzh/admin.git
    fetch = +refs/heads/*:refs/remotes/origin/*

生成Git SSH秘钥

对于 ssh 方式,如果没有设置秘钥,向远程分支 git push 的时候就会被拒绝。本地 git commit 不影响,照常可以使用。

打开命令行 cmd,输入下面的命令开始生产 ssh 的 rsa key:

ssh-keygen -t rsa -C "用户名"

接下来一直回车即可,使用默认设置,如果不是第一次生成,选择 overwrite。会在指定的目录下面生产秘钥文件:/c/Users/用户名/.ssh/id_rsa.pub,在步骤说明里面有详细的地址。

记事本打开这个文件,里面就是 rsa key。

将 ssh 秘钥添加到版本库中

生成 ssh 秘钥以后,需要在 github 或者 gitlab 上面添加 ssh key。

添加的位置在个人设置->SSH秘钥中。添加完之后,如果你有提交权限的话,就可以 git push了。

HTTP 方式设置保存用户名密码

对于 HTTP 方式的git项目,会在每次 git push 的时候要求你输入用户名和密码,这时我们想只输入一次就可以了,可以通过添加git配置,保存用户名和密码,这种方式用户名和密码都是明文保存,会稍微不安全。

# 设置永久保存
git config --global credential.helper store
# 设置记住密码(默认15分钟)
git config --global credential.helper cache
# 设置过期时间3600s
git config credential.helper 'cache --timeout=3600'

上面的命令相对于在 config 文件中添加配置:

[credential]
helper=store

另外还可以将直接修改配置文件中的 url 部分,将用户名和密码写到 url 中:

http://username:[email protected]/name/project.git

Git 版本管理流程

命名规范

  • master 分支:master;
  • develop 分支:develop;
  • release 稳定分支:release_stable_YYYYMMDD_n,YYYYMMDD 为当前日期(年月日),n 为递增序列号;
  • release 测试分支:release_nightly_YYYYMMDD_n,YYYYMMDD 为当前日期(年月日),n 为递增序列号;
  • 开发人员版分支:bug单号为 bugfree 编号,n 为递增序列号。
    • 修复 bug:fixbug_bug单号_邮箱前缀_n;
    • 需求功能:feature_bug单号_邮箱前缀_n。

基本命令

以下所有命令的开发分支号均以fixbug_12345_jingyang_1举例,合并均以合并到 develop 举例。

  • 切换分支:git checkout fixbug_12345_jingyang_1
  • 建立新分支并切换到该分支:git checkout –b fixbug_12345_jingyang_1
  • 查看所有分支:git branch -a
  • 推送分支到服务器:git push origin fixbug_12345_jingyang_1
  • 合并分支:git merge origin/fixbug_12345_jingyang_1
  • 删除分支:
    • 本地删除:git branch -d fixbug_12345_jingyang_1
    • 远程删除:git branch –r –d fixbug_12345_jingyang_1,删除后推送到服务器 git push origin :fixbug_12345_jingyang_1
  • 回退版本:git reset –hard HEAD^

基本思路

  • 开发人员分支:
    • 从release 稳定分支签出新分支;
    • 自测通过的分支会被合并到release 测试分支(合并专员不会合并有冲突的分支);
    • 最终被合并的无冲突release 测试分支会合并到 develop 分支。
  • 测试(QA)环境:测试develop 分支;
    • 测试结束后重新合并一个release 测试分支,剔除所有测试不通过的分支;
    • 版本会合并到release 稳定分支。
  • 生产(Prod)环境:进行冒烟测试;
    • 测试结束后重新合并一个release 稳定分支,剔除所有测试不通过的分支;
    • 通过的release 稳定分支会同步到局方环境;
  • 局方 CUC 环境:运行最终的 release 稳定分支。

未分类

注意事项

  • 合并release 测试分支过程中不进行冲突合并,遇到冲突即回退到上次提交;
  • 开发人员未被合并的分支,将在下一次另建分支合并;
  • release 稳定版应保留多个历史版本;
  • 应在版本被推送到局方环境之后再考虑删除开发人员分支。

场景

以下场景均假设有最近稳定版本release_stable_20171221_1,修改 bug 编号12345.

1、新建分支修改 bug

  • 拉取代码,确保获得最新版本:git pull;
  • 签出release 稳定分支分支:git checkout release_stable_20171221_1;
  • 在稳定分支基础上,新建自己的分支:git checkout -b fixbug_12345_jingyang_1;
  • 在自己的分支上修改代码,并提交。

2、合并分支冲突

合并过程中遇到冲突的分支将不会被合并。由于项目文件较多,冲突并不常见。如果遇有冲突,开发人员应该自己解决。

以下设想了两种场景,但无论是哪种场景,开发人员都应该找到冲突原因,协商解决。

  • 如果今天还有合并分支的机会:找到冲突原因,协商解决,确保下次合并没有冲突;如果已经发布了新的稳定版本,合并稳定版本到自己的版本
  • 如果赶不上今天的合并:签出最新release 稳定分支,并合并到自己的分支中,确保代码是最新的。
  • 合并release 稳定分支到自己的分支:git merge release_stable_20171221_1 fixbug_12345_jingyang_1

3、测试人员测试不通过

  • 放弃之前的分支fixbug_12345_jingyang_1;
  • 从最新的release 稳定分支签出新的分支:git checkout -b fixbug_12345_jingyang_2;
  • 把前一个分支合并到新分支 git merge fixbug_12345_jingyang_1 fixbug_12345_jingyang_2,并重新开始修改。

4、局方测试不通过

局方不通过时,通常代码已经被合并到最新release 稳定分支中。

  • 从最新release 稳定分支签出新分支:git checkout -b fixbug_12345_jingyang_2;
  • 在新分支上进行修改。

5、bug修改经历多日

记得在最终合并分支之前把最新的release 稳定分支合并到自己的分支。

PHP实现git部署

背景

小公司上班,原先的系统还在使用传统的ftp上传部署,部署太麻烦,也不好管控线上的代码.在网上找了下git部署的教程.一路跟着下来,碰到了不少坑.现在把整个过程发出来,希望可以帮助到大家.

账号相关

useradd -m git    //新增git账号

ssh-keygen        //密钥生成,如果已经有了可略过

su git       //切换至git账号

cd ~   //切换到git账号根目录

mkdir .ssh    //创建.ssh目录

cat /密钥存放目录/xxx.pub >> ~/.ssh/authorized_keys     //设置公钥

此处一定要注意权限问题,否则密钥登入无法生效,各文件夹对应的权限如下

.ssh文件夹权限   700
id_rsa   600
id_rsa.pub   644
authorized_keys  600

文件权限设置

将git与站点运行nginx或者apache的用户放同一用户组,如www
vim /etc/passwd 找到git账号与www账号,将git账号的组标识变更为与www组标识一致
站点所属者设置为git,用户组设置为 www //假设nginx与git 都归属于www用户组
目录权限设置775 ,文件权限设置为664

仓库代码

服务器端

cd 站点目录
git init   //初始化目录

git配置

git config receive.denyCurrentBranch ignore     //设置仓库接受代码提交

设置sudo免密

vim /etc/sudoers

# Defaults        secure_path 中若没有你要的命令,要注意添加
# php的命令默认需要在 secure_path最后面添加    :/usr/local/php/bin"
在  root ALL=(ALL) ALL 下面一行添加
git ALL=(ALL) NOPASSWD:ALL     
# 保存退出   ,这样针对laravel 要重启队列命令就可以使用了.
# sudo php artisan queue:restart

钩子设置

cd .git/hooks    //切换至站点钩子目录
touch post-receive    //创建接收提交时钩子
// 钩子文件内容如下:

#!/bin/sh
# 设置账号创建文件的默认权限
umask 002   
unset GIT_DIR
cd ..
git checkout -f
# 执行PHP钩子逻辑
/usr/bin/curl http(s)://域名/钩子文字位置/hook.php

# 如果有使用laravel队列则需要重启队列进程,让新代码生效
# sudo php artisan queue:restart
exit 0

hook.php内容

<?php
/**
 * git上传执行钩子
 */
//TODO 安全限制
//TODO 其他钩子行为
// 清除opcache
if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
    opcache_reset();
}

增加钩子可执行权限

chmod a+x .git/hooks/post-receive

本地代码

git remote add 远程仓库名称 ssh://git@IP地址:/站点目录    //添加远程仓库
git push 远程仓库名称  master 

特别注意

用户上传的图片目录一定要做好文件忽视动作,否则有可能在清除未追踪文件时将此部分文件删除,造成灾难性结果。

git 删除敏感文件流程

git目录中可能因为失误或者其他原因上传了敏感文件,需要从多个分支中删除敏感文件及其历史记录

流程

  • 使用filter-branch命令移除本地所有分支的敏感文件历史记录
  • 参数将会强制git处理而不是回退,所有分支及标签历史上的指定文件
git filter-branch --force --index-filter 
'git rm --cached --ignore-unmatch [敏感文件]' 
--prune-empty --tag-name-filter cat -- --all
  • 将敏感文件加入gitignore
echo "[敏感文件的根目录路径]" >> .gitignore
  • 强制更新远程仓库
git push origin --force --all