libgit2使用教程(三) git commit

首先去看 API 中关于 commit 的部分都有哪个函数比较像,但是意外的发现了 API 中并没有官方 101-samples 中的 git_commit_create 函数,搞得我好意外。不过据我所知,实现 commit 操作肯定是要使用这个 git_commit_create 函数了。那咱们先去头文件看看它需要什么参数:

·id 返回一个 OID 的指针,这个 OID 表示的就是新创建的这个 commit
·repo 仓库指针
·update_ref 需要提交的目标引用,使用当前分支的引用就用 “HEAD” 就可以了
·author 作者 git_signature 指针。git_signature 记录作者的名字、email、和一个时间
·committer 提交人也是 git_signature 指针,跟上边可以是同一个
·message_encoding 这次提交说明信息的字符集
·message 这次提交的完整说明
·tree 我们需要传入一个 git_tree 实例的指针,这个 tree 对象必须属于这个仓库。这个 ·tree 将是我们下边要解决的第一个问题
·parent_count 这个 commit 的父级的数量,如果是 merge 后的 commit 会有两个甚至以上,咱们暂时先不考虑分支的事情,所以只玩儿1个的
·parents 是一个 git_commit 指针的数组,表示上一级的提交,如果是第一次 commit 数组应该是空的,同时 parent_count 应该是0。如果不是,咱们就得先把爸爸找出来,这将是下边要解决的第二个问题

1. tree:
这个 tree 实际上就是把 index 的 entry 写到一个 tree 中,最终把这个 tree 提交。从 API 文档可以找到 git_index_write_tree 这个函数。它的作用就是写一个 tree,所以就可以写出下面的代码:

2. parent:
因为我们还先不考虑 merge 的情况,所以父级 commit 只有一个,这个父级实际上就是 head 引用,所以我们第一步先拿出 head。

这里我们要考虑一个情况:在一个全新的仓库的情况下,是没有之前的 commit 的,因此,取出来的 ref_head 会是空的。所以要做一个判断:

这样我们就拿到了我们需要的 tree 和 parent commit ,下面就是签名,然后提交了:

这样 commit 操作就完成了。

示例代码是 sample3
同样放在 github 的 libgit2_samples 中

libgit2使用教程(特别篇)几个基本概念说明

本来打算这篇写 commit 但是感觉涉及的一些概念不细说一下可能不太好,因此花了一下午时间整理了这些基础概念。对这些概念理解之后会比较有利于理解 git 仓库的结构,对于理解 git 系统的工作原理也会有很大的帮助。所以,这篇没有任何功能实现,纯描述概念,可能有些无聊,对于对下面这些完全没有概念同学,还得代码敲起来,自己动手探索才能理解到其中的精髓。

  • blob
  • oid
  • tree
  • commit
  • reference
  • branch
  • index

1. blob:

blob 对象直接包含 git_object ,但是 git_object 这个概念是封装起来的,我们一般情况下是接触不到的。它将直接对应存放在仓库中的数据文件,对于 blob 我们就把它直接理解成我们文件夹中的文件就可以了。它是整个 git 仓库管理的基础单位,作为实际文件的代表,可以说 git 的版本管理就是花式玩儿 blob 。

2. oid:
指的是 git_object 的 id。每个独立的 git_object 都有一个 id,id 相等则可以判定是同一个对象。
它存储的是一个 SHA-1 值,20个字节大小,每个字节存放一个16进制数。如果转成字符串,则是一个40个字符长的字符串,两个字符表示一个16进制数。
相互转换的函数:
git_oid_fromstr 把 SHA-1 转成 oid
git_oid_tostr 把 oid 转成 SHA-1

3. tree 和 tree entry:
tree 就如它字面上的意思,是一个树形数据结构,tree entry 就是这个树的节点。

可以用上边的方法简单遍历 tree 的一层,这些 entry 可能是没有子节点的“文件”,也有可能是还有子节点的“文件夹”,也就是说,tree entry 还可以作为 tree 持有自己的 tree entry。

通过 git_filemode_t 我们可以看出,entry 就可以表示一个 blob。

这里有一个要注意的地方,这个枚举的值是以8进制表示的,直接以10进制数方式打印,跟这个字面值是不一样的。

4. commit:
commit 是版本的基本单位,版本库的记录都是以 commit 为基础的,谁提交的、什么时候提交的、提交的说明信息、提交时的 tree 的状态都是由 commit 管理的。它还要知道它的父级 commit 是哪个或者哪几个,最终 commit 们会组成一个有向图。
我们要找某一个版本,就是去找这个版本的 commit ,有了这个 commit 我们就可以通过它调出那个版本当时的 tree,然后由 tree 管理的所有文件就都可以找到了。可以说 commit 是 git 版本管理的核心,其他所有的概念都是围绕着 commit 展开的。

5. reference:
这个引用怎么用语言来描述我一直很纠结。reference 引用的是一条 commit 链——更准确的说不应该是链,因为 commit 网上追溯有可能有多个父 commit 的情况。它实际上是一个倒过来的树,reference 是它的根节点,commit 的 parent 就是子节点。这是为了方便向上追溯版本。
实际上每多一个分支,就相当于多了一个 reference ,reference 就包含着这个分支的最新的一个 commit。
git 系统也预置了几个重要的 reference 名称,用于方便索引。其中最重要的就是 HEAD 了,在工程的 .git 文件夹就可以看到这个文件,打开看看,它只有一个指向一个 refs 文件夹的路径,实际上它指向的就是当前所在的分支。所以,通过 HEAD 就可以非常方便的找到当前的 reference。

6. branch:
在上边 reference 的部分我多次提到了“分支”这个词,然而了解一些 git 命令行工具的同学肯定知道,git 工具分支的命令是 branch ,而且日常沟通也都是以 branch 的概念来代表分支的。
这没有错,git 概念中的分支就是 branch ,同时 libgit2 这个库中也有 branch 这个概念,它所指的实际上就是 reference 。就是给 reference 起了个名字,这个名字是方便记忆的,而不是 reference 用的一个路径。

从这个函数声明就可以看出,通过一个分支名返回是一个 reference ,同时,在函数实现的内部,也是通过 git_reference_lookup 实现的。因此可以看出,branch 实际上就是一个有名字的 reference 。

如果此时 HEAD 就是 master 分支的话,roid 和 boid 的值是相等的。

7. index 和 index entry:
index 索引的是当前工作区中未提交的内容。完整的 commit 操作就是将 index 中的内容写到一个 tree ,然后用这个新的 tree 创建一个新的 commit ,然后更新 reference 。而在这之前的 add 操作,就是将改动更新到当前 index。
index entry 就像 tree entry 也是作为文件的代表,而不一样的地方就是,单独的文件夹不再是 tree 了。可以通过它的 mode 属性,看出每一个文件是一个 blob ,submodule 是另外一个 blob。
实际上在没有改动的情况下 index entry 和 tree entry 是相同的 blob 。只有有新的 add 才会使同一个文件对应的 blob 不同。

初撸WordPress小记

撸这个 WordPress 站有一段时间了,也解决了一些比较蛋疼的问题,大晚上的一股鸡血上脑,就撸了一发小文,记录一下这段时间解决的几个比较有用的问题。

1. 中文标签和分类的问题
偶然发现直接点击中文标签或分类链接,wordpress 是根本找不到相关内容的。相当于访问 http://ftxtool.org/index.php/category/技术随笔/ 的时候服务器给我返回的是 404 ,然而,使用 http://ftxtool.org?category=技术随笔 传参数的方式却可以正常访问到,估计是 url 地址重定向时候的问题。
所以,网上能找到一类解决方案就是修改 rewrite.php 的代码。然而,就像某个复制粘贴的博主说的那样,根本没有找到别人说的那块代码在哪。应该是经过很多版本的更新,那部分代码早就不一样了,然而这种复制粘贴的精神依然屹立,永不过时。
另外一个解决方案,是修改 class-wp.php ,把 $_SERVER[‘PATH_INFO’] 和 $_SERVER[‘REQUEST_URI’] 转码,把 GBK 转成 UTF-8 。然而,经过测试好像并没有什么卵用。
方案三,修改别名。把别名改成一小串英文,亲测有效。具体操作方法就是:
仪表盘→分类目录/标签→选一个具体的分类或标签名字,点下边的快速编辑→别名(随便写个英文的名字),然后更新就好了。
方案四,插件。方案三虽然可以解决问题,但是每个标签都要手动改一遍还是有些蛋疼的。找来找去找到个插件,用起来还不错。插件名叫:IIS Chinese Tag Permalink 直接去安装插件里搜索这个名字就能找到。目前用起来还没啥问题,应该算比较靠谱的一个方案。然而还是有个别标签一直 404 ,只能通过改别名的办法解决,不知道有没有更好一些的插件可以解决这个问题。

2. sitemap
赵家百度的 sitemap 插件简直渣到爆,跟 google 的 XML-Sitemap 插件简直没法比。从设置页面就已经被秒成渣渣了。最蛋疼的是生成 sitemap 文件,我去点了按钮也没反应,一直是:没有生成 sitemap 文件。麻痹的没有任何提示了,只好去代码里下 log。
原来生成文件是在 wordpress 的跟目录,代码注释里竟然叫我把目录整个权限设成777。简直日了狗,你家运维都是这么干活的么?
然后,还是手动在根目录创建了个 sitemap 文件,然后加个写权限,就可以用了。
然而,还是 google 家的 sitemap 好使。自己自动生成好之后还能自动通知 google 和 bing 。

3. 友情链接
新版本添加友情链接还是蛮蛋疼的,网上能搜到的基本上都是老版本的方案,或者 link manager 插件,但是插件并不好使。真心想不明白,这帮人复制来复制去也没有经过验证,然后还被搜索引擎排的那么靠前。
新版本添加链接其实很简单,只要在侧边栏加上一个自定义菜单就可以了:
外观→自定义→小工具→侧边栏
点击【添加小工具】选择【自定义菜单】
然后选上自己添加好链接的菜单就可以了。

然而怎么添加一个菜单呢?
外观→菜单
点击【创建新菜单】
然后 填一个名字
然后 在【自定义链接】中添加
能看懂提示文字基本上不会错。

注意:这个菜单不要勾选 主菜单 。

libgit2使用教程(二) git add

主要内容:
1. git add <path>
2. git add .

示例代码:sample2

一、 open
上一篇初始化了一个 git 仓库,接下来开始使用我们本地的 git 仓库。几乎所有的操作都需要使用一个 git_repository 指针,所以第一步,我们先初始化这个指针。
如果是新创建一个仓库的话,使用上一篇的 git_repository_init 就可以了。如果是一个已有的仓库呢?
就用 git_repository_open 这个函数。这个函数很简单,两个参数,参数一是返回 git_repository 指针,参数二是仓库地址。

二、 add
add 就开始有些复杂了,我们要分情景讨论这个事。
1. add 指定文件
通过官方API我们找到了一个函数 git_index_add_bypath ,主要意思就是:通过指定 path 添加一个文件到 index [附一]
只有两个参数,参数一是 git_index 指针,参数二是指定文件的路径。
所以,我们要先拿到 git_index 的指针:

拿到了 index 指针,就可以 add 了:

然后运行一下,没有报错,然后到我们的仓库调用 git status 看一下。

On branch masterInitial commit

Untracked files:
(use “git add …” to include in what will be committed)

file

nothing added to commit but untracked files present (use “git add” to track)

file 并没有被 add 进来,是什么原因呢?

其实,git_index_add_bypath 操作并没有失败,只不过它是对内存的操作,在程序结束的时候,并没有把内存中的 index 写到磁盘,如果要把 index 写到磁盘,需要调用一个函数:

好了,目前完整的 add 的代码就是:

2. add 全部
使用函数 git_index_add_all 可以把没有添加到 index 的改动全部添加到 index 中。写一个简单的测试代码:

有时候对于参数不知道到底要传什么,就可以先传个 null 或者 0,运行看程序给你报什么错,再根据具体的错误找解决方案。不过显然,这段代码的运行结果是符合我们的预期的,我们对这个仓库中所有文件的修改都被 add 到 index 中了。说明参数二 paths 传空,就相当于命令:
git add .
那么,后边两个参数的作用是什么的?
倒数第二个参数是一个函数指针 git_index_matched_path_cb ,而最后一个参数则是传递给这个回调函数的参数。
所以我们给 index 添加改动的时候,是可以通过这个回调函数获得一些信息的:

这个回调的参数一是有改动的文件路径,参数二是 git_index_add_all 的第二个参数中和这个文件路径匹配的 pathspec ,第三个参数是前边提到过的自定义参数。
最重要的是它的返回值:
0:正常添加
正数:跳过添加这个文件的改动
负数:直接报错返回,这个时候 git_index_add_all 将直接返回,返回值就是我们这个回调函数的返回值。

所以这个回调函数可以让我们根据自己的情况对批量添加做一个过滤。
然后,翻回头再说一下 git_index_add_all 的第二个参数[附二]。他是一个路径规则,不满足这个规则的文件将直接被跳过。也不会触发上边那个回调函数。满足规则的文件和对应的那一项规则,将成为回调函数的参数一和参数二。
所以调用的代码是这个样子的:

 

附一、解释一下 index
index 可以理解为一个存储区,存放被 add 进来的“改动”,add 的文件的改动被添加到 index ,commit 操作把这个 index 添加到本地仓库,可以通过 git rm —cached 从 index 中删除指定“文件”。只要不 commit 我们玩儿的就一直是同一个 index 。

附二、git_strarray
git_strarray 是一个结构体:

strings 是一个字符串数组,count 是这个数组有多少个元素。
这个结构后边会经常用到,与多个路径相关的参数和返回值,都会使用这个结构。

libgit2使用教程(一)实现 git init

主要内容:
1. 使用 libgit2 的准备工作
2. 构建和运行
3. 初始化一个 git 仓库

示例代码:https://github.com/XiaochenFTX/libgit2_samples

进入正题

开始直接到 libgit2 的 readme 我们首先需要从那里获取一些有用的信息。
官网:https://libgit2.github.com/
API:http://libgit2.github.com/libgit2/
虽然官方文档写的挺水的,不过毕竟可以获得信息的途径就这么少,所以凑合着也还能用。

接下来提到了两个非常重要的函数, init 和 shutdown ,必须保证在任何操作之前调用初始化:

释放 init 申请的资源,使用:

这样就可以开始第一个程序了

ok 打完收工。

先把它跑一下看看,如果可以正常跑完,就说明所有的构建基本上都没啥问题了。

当然,这并没有什么卵用。接下来来搞个有意义的代码来结束这篇。
第一个例子就实现一下 git init

打完收工。

当然,良好的编程习惯,在最后不要忘了释放资源。

接口的详细说明可以去查官方的 API 我会在必要的地方对一些我觉得比较重要的东西进行说明。

git_repository_init 的第一个参数是将初始化好的 git_repository 指针返回,这个库的接口基本上都是这个风格的,以后就不再提这个事了。
这个函数会返回一个错误码,返回值为 0 表示执行成功,小于 0 表示有错误。所以可以通过判断返回值得方式来确定函数调用是否成功。
想知道具体报错信息,可以调用 giterr_last() 这个函数返回一个 git_error 结构体指针。

klass 对应 git_error_t 的枚举值,用于说明是哪部分出的问题。message 是一个人类可以看懂的的说明信息。开发中遇到问题可以把这个信息打印出来,对找问题有很大帮助。

最终完成 sample1 的代码就是这个样子

最后解释一下为什么要用 goto
很多同学在学习 C 语言的时候,如果遇到不负责任的老师,都会强调不要用 goto 这个东西,更有甚者干脆讲都不讲。而具体原因不外乎:会让代码逻辑混乱,可读性差,不好调试
当然,造成这些后果,都是在“如果用不好”的前提下的。然而,我认为这个语言特性真的不是不值一提的垃圾,把 goto 用好在一定程度上是可以让代码更美观、更简洁、更易读。
随便举个例子,比如跳出多层循环,难道还要引入一个外层变量再逐层判断吗?在这种情况下那种方式可读性更高?
还有就是在错误发生的情况下,直接跳到函数结尾进行清理。如果不使用 goto ,我见过几种奇葩方案,最突出的应该是用 do{}while(0); 吧,这样强行为了不用 goto 而产生的奇葩行为,我只能呵呵了。
所以,我推荐在必要的时候使用 goto 来使代码更清晰、简洁。当然,也不是随便瞎用,任何工具都有其最佳适用范围,不能矫枉过正。

附一、代码中引用 libgit2:
在根目录的 CMakeLists.txt 中把相关工程文件夹都用 add_subdirectory 加进来,并且指定 libgit2 的 include 为引用目录

在需要链接这个库的工程中使用 target_link_libraries 链接上 libgit2
就像我们的 sample1 中这样

有些环境下会报找不到 -lssh2 的错误:
ld: library not found for -lssh2
只要在根目录的 CMakeLists.txt 中加上一句:

这样简单的构建系统就搭建好了,可以开始写代码了
使用其他 IDE 的话,直接用 cmake 导出对应的工程,在自己创建的工程中引入就可以了。
不太推荐直接编译好库放到工程里使用,我自己试了之后发现它用到的几个第三方库还需要自己手动构建再引用。

附二、示例代码的使用:

1. 从 github 上拉代码

2. 使用 cmake 导出熟悉的 IDE 工程,或者直接构建运行

Xcode:

Visual Studio:

直接构建:

我使用的 IDE 是 Clion ,使用 Clion 导入工程也可以直接使用

MacOS OpenGL窗口程序的正确打开方式

当小白挖开gl渲染这个坑的时候,最先遇到的问题一定是窗口。然后就是虐死强迫症系列的剧情。
教程最多的一定是glut系列,然而,放到Xcode里一片黄,原因是苹果在未来要不支持这玩意了。再之后就是glew、glfw等一堆库,到这个时候一个没什么基础的小白玩家应该已经有流失的想法了。
这篇博客主要介绍怎么直接搞出一个干净的glview窗口程序,简简单单的开始OpenGL的新手引导。

然后也可以解决一些问题:
1. 如何创建一个OpenGL窗口
2. glut的警告问题
3. 可以使用4.0以上版本的OpenGL
4. 对于非小白玩家而言可能第3条是最重要的

废话少说,书归正传:
第一步、创建工程
1. 新建一个Cocoa Application工程
130298
2. 拖一个OpenGL View 替换掉原来的默认View
582077
这样这个窗口就可以启动运行了。

第二步、写代码
1. 新建一个Cocoa Class,选择继承自 NSOpenGLView
306506
2. 把上边建好的GLView关联上我们自己的类
742882
这样就可以在我们自己类中调用渲染代码了。

第三步、支持GL4.1
需要实现的函数:
initWithCoder
defaultPixelFormat
prepareOpenGL
drawRect

重点就是在初始化部分,需要设置一下属性才能真正的支持4.1

这样就是一个可以正常撸的GL窗口程序了。非常适合强迫症玩家入门使用,编译器完全没有警告。

在这就不贴完整代码了。有一个可运行的程序,在github上。
https://github.com/XiaochenFTX/glFirst

curl在Android系统卡死的问题

问题描述:
调用 curl 获取消息头的时候,发现程序卡住不动了。代码如下:

经测试发现,不做任何设置,做一次发送,同样会卡死。

解决方案:

经过反复试验,在请求的时候设置写数据回调,就可以正常执行了。

 

 

具体原因不详,暂时没有精力去读它的代码。我猜卡死的问题与代码本身关系不大,暂时比较怀疑编译库的工具链。由于之前遇到过 ndk 编译出的 sscanf 等函数调用卡死,所以这货暂时列为A级怀疑对象。

C++ 如何让自己的类支持foreach

首先,我们先看看使用 foreach 的时候,都发生了什么。

新建一个类,根据经验,我们 for 一个对象应该是这样的:
所以,迭代器, beigin(), end() 应该是会用得到的,我们做个试验:
Generator.h

Generator.cpp
main.cpp
运行结果:

 

说明我们想的没错,foreach 跟上边的 for 的行为是一样的。
插个体外话,(发现字打错了,不过不改了,还是挺哏儿的)
1. 之前以为 for (const auto i : g) 会调用 const 版本的 cbegin cend ,然而 并不会。
2. iterator 叫什么名字都没什么所谓,起名叫 红太阳 也ok。
接下来,处理一下迭代器:(我想确认一下丫具体被如何操 做)

 

输出结果:

已经足够说明问题了,自己把剩下的细节补充上就好了。

总结一下要点:
1. begin() end()
2. 迭代器
3. 迭代器重载 !=
4. 迭代器重载 前++
5. 迭代器重载 *(解引用)

iOS命令行打包的坑

最近在搞自动打包的时候,不小心踩到了了烂水果没有来得及擦干净的菊花。

总结一下经验教训:
1.技术
找不到ResourceRules.plist
类似这样的警告:
Warning: –resource-rules has been deprecated in Mac OS X >= 10.10! /tmp/QYFSJIvu7W/Payload/XX.app/ResourceRules.plist: cannot read resources
经过我缜密的调(gu)查(ge)取(bai)证(du),大致上可以猜测是烂水果更新了签名机制后并没有更新整套命令行工具。
秘密就藏在……
执行以下命令:

定位到 PackageApplication 然后用随便什么文本编辑器打开

搜索 ResourceRules ,定位到之后,清理掉与她有关的参数,整成这样:

就这样。

亲测有效,目前尚未发现副作用。
参考文献:(临时找来凑数的)
2.
签名验证失败:
Program /usr/bin/codesign returned 1 :
resource envelope is obsolete 这个错误
stackoverflow 上大神给出的解决方案就是在 codesign 验证的时候,加上 –no-strict (不严格验证?这尼玛确实不报错了,然而……总之就是不报错了)
具体操作方式,也是向上边一样,修改 PackageApplication
找到 —verify 那行 加上 –no-strict 参数:

参考资料: