git merge 当整个文件conflict的时候

背景

啊呀为什么我的pull request里面有整个文件全部conflict了?

仔细看个个commit,明明只是修改了文件中的一部分呀?

Git Merge 之后的差分

假设我们现在branch名为feature,目标branch为master。

为了试着修正conflict,我们试着[feature]$ git pull master

这个时候产生了整个文件的冲突。vim打开冲突文件。我们发现每一行末尾都有^M

但是为什么当我们vim单独打开feature branch下的这个文件的时候,却什么都看不到呢?

fileformat

众所周知不同的os的默认换行是不一样的。

当我们单独打开feature branch下的目标文件,发现其格式为utf-8[dos] 而位于master下面的目标文件,其格式为

utf-8[unix],这个差异造成了我们上述的整个文件的conflict。

解决方法很简单,用vim打开在feature branch下的目标文件。

1
:set fileformat=unix

即可。

后记

不同OS下的换行。

1
2
3
4
5
6
7
LF・・・UNIX
CR・・・MacOS(version 9之前,现在已经不常用)
CR+LF・・・Microsoft Windows

CRLF : ↵
CR  : ←
LF  : ↓
vim

Apache httpd 最近的一些问题录以及解决方法

http_response_code 的困惑

相同问题也在StackOverflow自问自答了。

在使用php-fpm 和 httpd 的时候我发现了个奇怪的现象。

如下面的例子。

1
2
3
4
<?php
$result = http_response_code(200);
var_dump($result);
phpinfo();

当设置response为200的时候,什么问题也没有,访问这个扔在web根目录的php文件,会成功在页面上输出dump的内容以及phpinfo的内容。但是,当我们把200改成400的时候,就出现了httpd自带的400页面。

这种现象给人产生一种错觉,那就是这个页面执行到http_response_code(400) 就中断了。因为同样尝试在非php-fpm的配置下执行相同的php脚本,是可以看到dump输入400的内容以及phpinfo的。

解决这个问题的关键是,你确定改脚本执行到http_response_code(400)就中断了吗?因为没有输出在页面上,所以很容易就产生了脚本中断的错觉。

验证这个错觉的方式很简单,把var_dump 改成向某个文件里面输入一些内容就行了。实验也证明的确文件中存在着我们输出的内容。

那么其实httpd做的事情就很明显了。

当我们返回非正常状态的时候(4xx,5xx) httpd匹配了类似下面的配置。

1
2
3
ErrorDocument 403 /error.html
ErrorDocument 404 /error.html
ErrorDocument 500 /error.html

那要如何取消这种默认的匹配而让我们的脚本来处理这种错误呢? 在config里面赫然发现了ProxyErrorOverride on 这个设定。把on设置称off。相当于告诉httpd 我们php-fpm的错误状态还是交给php-fpm来处理。

ProxyPass and ProxyPassReverse

在httpd中设置反向代理的时候我们常看到下面两行配置。

1
2
3
4
5
6
<VirtualHost myhost:80>
ServerName myhost
DocumentRoot /path/to/myapp/public
ProxyPass /app https://myapp:8080/
ProxyPassReverse /app https://myapp:8080/
</VirtualHost>

既然使用了ProxyPass ,那为啥要有ProxyPassReverse呢?

ProxyPassReverse是用来改写后端返回结果的header的。

当有如下302的跳转

1
https://myapp:8080/ => https://myapp:8080/new

我们服务器发送 /app 实际的导向会变成 /new, 而正确的导向应该是/app/new

所以ProxyPassReverse起到了查看后端返回的header,把里面的东西反过来加上/app

WKWebView中的Usercript插入

背景

ios端的浏览器没有插入Userscript机制。为了满足我们时常精虫上脑的需求,我们可以自己写一个基于WKWebView的应用来插入我们的Userscript。

方法

  1. 建立WKUserScript。
1
2
3
4
5
6
7
8
9
10
11
var jsSource = ""
let path = Bundle.main.path(forResource: "user_script", ofType: "js")!
if let data = NSData(contentsOfFile: path){
jsSource = String(NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue)!)
}
let userScript1 = WKUserScript(source: jsSource, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)

let controller = WKUserContentController()
controller.addUserScript(userScript1)
let configuration = WKWebViewConfiguration()
configuration.userContentController = controller

上面这段是我抄来的。我们需要将user_script.js放置在app project的根目录下。Swift这命名我特么也是要吐了。

  1. 创建WKWebView的实例
1
self.webView = WKWebView(frame: view.bounds, configuration: configuration)
  1. 调整WKWebView大小来迎合Device样式(这里就不废话了)

  2. load我们需要的插入Userscript的页面

1
2
3
let webUrl = URL(string: "https://某小电影网站")!
let myRequest = URLRequest(url: webUrl)
webView.load(myRequest)

快速生成icon

这个工具好评家鹅。

直接把png放到app的根目录然后app-icon generate就行了。

后话

飞速地写好了。然而并不打算公开。

获取当前Spotify播放歌曲的歌词

背景

标题是お盆休み之前给自己布置的作业。

很多时候上班带着耳机写代码,听到不错的歌曲很自然而然想去找歌词。Spotify这种垃圾东西,有歌词的歌曲又极其少,而且当你在termnial敲代码的时候自然不想离开termial。
这个时候,敲个命令就能显示当前播音乐的歌词多好。

解决方案设计

想要获取当前的歌曲的歌词,自然先得获取当前的歌曲名字(track)和专辑名(album)以及歌手名(artist)。

乍一想难上天啊,如果spotify不提供什么机制的话,这个就是强制进程通信啊。

还好,spotify有web console这种东西可以支持操作现在的播放设备。这里就不废话了,没有采用的理由是access token的默认寿命只有1小时。🤣

其实OS X提供了一个很牛逼的工具可以让我们达到获取歌曲名,歌手以及专辑名的方法。那就是osascript

1
2
3
4
5
6
➜  my_blog git:(master) ✗ osascript -e 'tell application "Spotify" to artist of current track as string'
Park Boram
➜ my_blog git:(master) ✗ osascript -e 'tell application "Spotify" to name of current track as string'
Will Be Fine
➜ my_blog git:(master) ✗ osascript -e 'tell application "Spotify" to album of current track as string'
Will Be Fine

wow,crazy. 接下来就是找个搜歌词的接口把这些塞进去就是了。

Genius 这个网站提供了搜歌词的接口,注册之后可以免费使用。从搜索的返回结果中找到歌词网页的path,最后用爬虫爬取页面获得歌词即可。

Demo

调用osacript和获取歌词path这些在shell脚本中完成即可。至于爬虫有些麻烦,我用nokogiri写了个ruby脚本,将歌词path作为参数传给ruby脚本处理最后输出歌词。

1
2
3
4
5
6
7
8
9
10
11
12
13
#! /bin/sh

artist=`osascript -e 'tell application "Spotify" to artist of current track as string'`;
track=`osascript -e 'tell application "Spotify" to name of current track as string'`;
album=`osascript -e 'tell application "Spotify" to album of current track as string'`;

lyrics_data=`curl -s "https://api.genius.com/search?q='"$track $artist $album"'" -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer your_own_token" `

lyrics_path=`echo $lyrics_data | jq ".response.hits[0].result.path"`

song_title=`echo $lyrics_data | jq ".response.hits[0].result.full_title"`

ruby ./lyrics_parser.rb "$lyrics_path" "$song_title"

ruby 脚本就是个爬虫,最后输出歌词即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "nokogiri"
require "open-uri"

url = "https://genius.com#{ARGV[0]}".gsub('"',"")

html = open(url) do |f|
charset = f.charset
f.read
end

doc = Nokogiri::HTML.parse(html, nil, nil)
doc.xpath('//div[@class="lyrics"]').each do |node|
puts ARGV[1] + "\n"
puts node.inner_text
end

运行效果如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
➜  cmd_lyrics ./lyx.sh
supercell
君の知らない物語
https://genius.com/Supercell-kimi-no-shiranai-monogatari-lyrics
"君の知らない物語 (Kimi no Shiranai Monogatari) by ​supercell"



Itsumodoori no aruhi no koto
Kimi wa totsuzen tachiagari itta
"konya hoshi o mi ni yukou"

"tamani wa ii koto iunda ne"
Nante minna shite itte waratta
Akarimonai michi o
Bakamitai ni hashaide aruita
Kakaekonda kodoku ya fuan ni
Oshitsubusarenaiyouni


Makkurana sekai kara miageta
Yozora wa hoshi ga furu youde


Itsu kara darou kimi no koto o
Oikakeru watashi ga ita
Douka onegai
Odorokanaide kiiteyo
Watashi no kono omoi o
....

渣渣渣。

后续

这个世界上本来就不存在一个很好的歌词库。

以上。

FujiRock 2018 计划

交通

7月26日18点,二子玉川出发。

環七=>上越道=>三国街道

下高速吃晚饭。月夜野IC。地点待定。

预计到达 23点。

携带物品

帐篷

折叠桌 x 1,折叠椅 x 2

睡袋 x 1

雨衣裤

中裤 x 2

T恤 x3

内衣内裤洗漱用品毛巾

矿泉水 500ml x 24

纸巾

湿巾

防晒霜

除虫驱蚊喷雾

移动电源

露营灯

铺垫

剪刀

水箱 10L

耳塞

眼罩

枕头

USB电风扇

TBD

温泉

猿ヶ京温泉温泉街

宿場の湯

备忘

TBD

Docker的CMD和ENTRYPOINT的区别

这个问题很基本。至于为什么到现在才想去弄清楚,大抵的理由就是平时不怎么用docker。用也是直接当应用程序一样一跑而已。

简而言之,ENTRYPOINT 指的是,docker container在启动的时候跑的命令。

任何docker container,如果不指定ENTRYPOINT,默认的是运行/bin/ch -c {cmd}

括号里的内容,可以是通过CMD定义的参数, 也可以是通过docker run -i -t image_name <cmd> 传递的参数。

于是,我们自然可以自定义ENTRYPOINT。

需要注意的是,无论是CMD还是ENTRYPOINT,都是只有最后定义的那个才是有效的。

以上。

你不懂MV

我们常常用mv source/a target/b 来重命名。 a,b 都是directory。
然后很多时候我们忽略了一个重点就是这个操作重命名成功的条件是b不存在或者为空。
当b不为空的时候,a 会跑到b的下面。target/b/a

如果这个时候想用a里面的内容替换b的内容,那就是老老实实rsync了。

rsync -av source/a target/b

咦为什么a还是跑到了b的下面?

其实正解是

rsync -av source/a/ target/b

写到这里,真是羞愧地低下了头。

Truffle使用web3.js调试智能合约的一些备忘

进入develop调试模式

1
2
$ truffle compile && truffle develop
$ deploy

deploy之后,我们可以在testrpc上调试智能合约。

获取地址balance

1
2
truffle(develop)> web3.eth.getBalance('0x627306090abab3a6e1400e9345bc60c78a8bef57').toNumber()
96722887800000000000

注意balance的单位是wei。转换成eth则是

1
2
truffle(develop)> web3.fromWei(96722887800000000000, 'ether')
'96.7228878'

payable的函数调用

1
Loto.deployed().then(function(instance){return instance.Buy(5, {value: web3.toWei(0.1, "ether")});}).then(function(value){return value});

纯Get函数调用

1
Loto.deployed().then(function(instance){return instance.getSlots.call();}).then(function(value){return value});

从ERC20 到 ERC721

ERC20的问题点

我们熟悉的基于以太坊的ICO,通常使用ERC20标准发行Token。

ERC20标准的Token主要有两个问题。

第一个问题是,如果错误地将ERC20的Token发送到了非用户地址(比如合约地址)上,大多数情况下,该合约不具备处理相应Token的能力,所以这笔Token就看永久性丧失了。

第二个问题,基于ERC20发行的Token,相同的Token都具有可替代性(fungible), 拿简单的例子来说,我拥有的5ETH和别人拥有的5ETH并没有本质上的区别,相同条件下都可以在交易所换成相同数量的法币。这个性质本身没有什么问题,但是就Token的使用来说,其功能就会受到很大限制。

ERC223 解决了第一个问题。当发送者错误发送到不支持相应Token的合约地址上的是时候,错误发送的Token可以允许被召回。ERC223的细节就此不展开了。我们主要集中讨论第二个问题。

ERC721 以及 Non-fungible

ERC721是Non-fungible Token的标准。著名的以太坊上的游戏CryptoKitties就是基于ERC721的Token。现实生活中,有很多价值是Non-fungible的,最简单的例子就是你在游戏里抽的扭蛋。

ERC721与ERC20的不同之处,主要有两处。

第一,任何一个Token都有相应的一个id,合约本身会用一个mapping记录token的id以及相应的所有者。

为了满足Non-fungible的特性,任何一个Token都会有相应的MetaData。直接将每一个Token本身的特性记录在Blokchain上通常代价很昂贵,所以,我们通常的做法是保持一个Token id和ipfs或者外部https链接的mapping。例如下面的例子。

1
2
3
4
5
6
contract MyNFT {
mapping(uint256 => string) tokenLinks;
function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl) {
return tokenLinks[_tokenId];
}
}

参考资料

更加进一步的说明可能比较多余。建议直接看下面的参考资料。

参考1

参考2

参考3

使用Truflle开发基于oraclize的应用

Oraclize 是什么

Oraclize是将外部的变化引入Blockchain的一套工具。Oraclize已经部署到了很多Blockchain当中,
最常见的,在Ethererum上,Oraclize是一个智能合约。
直接安例子比较直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity ^0.4.11;
import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";

contract ExampleContract is usingOraclize {

string public EURGBP;
event LogPriceUpdated(string price);
event LogNewOraclizeQuery(string description);

function ExampleContract() payable {
updatePrice();
}

function __callback(bytes32 myid, string result) {
if (msg.sender != oraclize_cbAddress()) throw;
EURGBP = result;
LogPriceUpdated(result);
}

function updatePrice() payable {
if (oraclize_getPrice("URL") > this.balance) {
LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
} else {
LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
oraclize_query("URL", "json(https://api.fixer.io/latest?symbols=USD,GBP).rates.GBP");
}
}
}

上面的智能合约调用了oraclize_query这个oraclize提供的方法。oraclize会监听整个Ethereum Blockchain,在区块链外调用这个query,获取到结果之后,调用目标合约的__callback方法并把query的结果作为参数传入。

有了oraclize,我们想要在blockchain内部调用外部api的时候,只需要写好相应的callback函数就行了。

用Truffle将上例的智能合约部署到Rinkeby TestNet

其实下面的内容和oraclize关系不大。不过既然尝试了解oraclize,不如将其部署之后熟悉一下中间的步骤。

首先是需要的东西。

  • Mist 钱包以及testnet的账号。注意同步最新的内容。

  • Geth

  • Rinkeby Testnet的Eth。网上所以下faucet一下就出来了。

用Geth 跑起local 节点

Mist自带的Geth已经同步了最新的链上的内容,我们需要关掉mist,然后在local用Geth跑Rinkeby的节点。

1
geth --rinkeby --rpc --rpcapi db,eth,net,web3,personal --unlock="0x889a183da0d5450CCbC4604a8200eEb47020f719" --rpccorsdomain https://localhost:3000

注意要输入解锁账号的phrase。

建立truffle项目进行配置

1
2
3
$ mkdir truffle-test
$ cd truffle-test
$ truffle init

需要修改truffle.js的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*", // Match any network id
},
rinkeby: {
host: "localhost", // Connect to geth on the specified
port: 8545,
from: "0x889a183da0d5450CCbC4604a8200eEb47020f719", // default address to use for any transaction Truffle makes during migrations
network_id: 4,
gas: 4612388 // Gas limit used for deploys
}
}
};

然后书写我们的合约。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# contracts/Example.sol

pragma solidity ^0.4.11;
import "./oraclizeAPI.sol";

contract ExampleContract is usingOraclize {

string public EURGBP;
event LogPriceUpdated(string price);
event LogNewOraclizeQuery(string description);

function ExampleContract() payable {
updatePrice();
}

function __callback(bytes32 myid, string result) {
if (msg.sender != oraclize_cbAddress()) throw;
EURGBP = result;
LogPriceUpdated(result);
}

function updatePrice() payable {
if (oraclize_getPrice("URL") > this.balance) {
LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
} else {
LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
oraclize_query("URL", "json(https://api.fixer.io/latest?symbols=USD,GBP).rates.GBP");
}
}
}

我们需要在local应用oraclize。

1
2
3
$ cd contracts
$ wget https://raw.githubusercontent.com/oraclize/ethereum-api/master/oraclizeAPI_0.5.sol
$ mv oraclizeAPI_0.5.sol oraclizeAPI.sol

接下来在migrations下面添加2_deploy_contracts.js

1
2
3
4
5
var Example = artifacts.require("ExampleContract");

module.exports = function(deployer) {
deployer.deploy(Example);
};

这样智能合约的内容就完成了。
rk rinkeby

a 译truffle compile, 部署 truffle migrate --network rinkeby

大功告成。

用Truffle console 调试

1
2
3
4
5
6
$ truffle console --network rinkeby
truffle(rinkeby)> ExampleContract.deployed().then(function(instance){return instance.EURGBP.call();}).then(function(value){return value});
'0.87523'
truffle(rinkeby)> ExampleContract.deployed().then(function(instance){return instance.updatePrice({value: web3.toWei(0.01, "ether")});}).then(function(value){return value.logs[0].
args});
{ description: 'Oraclize query was sent, standing by for the answer..' }

最后如果要监听回掉函数里面的Event,可以用下面的方法。

1
ExampleContract.deployed().then(meta => { const allEvents = meta.allEvents({ fromBlock: 0, toBlock: 'latest' });allEvents.watch((err, res) => { console.log(err, res);});});

以上流水账仅供备忘。