覽器和 JavaScript 的功能逐年不斷的變強(qiáng)變大。曾幾何時,任何類型的游戲都需要Flash。但隨著 HTML5 發(fā)展,HTML5 + WebGL 游戲式就慢慢占領(lǐng)著這個舞臺。以下是30款流行的游戲,它們可以在所有現(xiàn)代瀏覽器中運(yùn)行,并且只使用web技術(shù)構(gòu)建。
地址:http://hexgl.bkcore.com/
類型:街機(jī),賽車
HexGL 是一款基于HTML5,JavaScript和WebGL的快節(jié)奏的賽車游戲。玩家可以使用鍵盤,觸摸屏設(shè)備或leap motion(體感控制器)來控制太空飛船。
地址:http://www.cross-code.com/en/home
類型:動作,角色扮演
一個復(fù)古靈感的2D游戲設(shè)定在遙遠(yuǎn)的未來。這是一個充滿偉大的游戲機(jī)制,如組合,拼圖,技能樹,任務(wù),物品等等。
地址:https://sketch-out.appspot.com/
類型:街機(jī)
Sketchout的任務(wù)保護(hù)你的行星,并通過改變流星的方向來消滅對手,通過使流星偏轉(zhuǎn)來保護(hù)您的星球并消滅對方,這款游戲有很棒的視覺效果和音樂特效。
地址:http://www.treasurearena.com/類型:多人,角色扮演,動作
Treasure Arena 是一款動態(tài)的競技場戰(zhàn)斗游戲,最多可容納4名玩家。它具有不同的游戲模式,出色的幀率和配樂,是一個非常有趣的游戲。
地址:http://bejeweled.popcap.com/html5/
類型:街機(jī),解謎,娛樂
HTML5格式的經(jīng)典“寶石迷陣”游戲。這是一個官方克隆,因此可以正常運(yùn)行且外觀完美。
地址:http://missile-game.bwhmather.com/類型:街機(jī)
這是一款非常具有挑戰(zhàn)性的游戲,游戲中我們扮演的是一枚被發(fā)射進(jìn)隧道的導(dǎo)彈。游戲有很酷的黑白圖像,玩的時候會有很強(qiáng)的場景效果。
地址:http://www.deconstructeam.com/games/gods-will-be-watching/類型:拼圖
在這個令人毛骨悚然(但又很棒)的游戲中,我和自己團(tuán)隊(duì)必須獨(dú)自生存40天。團(tuán)隊(duì)有六名成員,其中包括一只狗,一名精神病醫(yī)生和一個機(jī)器人,您必須與他們互動,以使其保持溫暖,溫飽和理智的狀態(tài)。
地址:http://www.sinuousgame.com/類型:街機(jī)
一個簡單的游戲,極簡的圖形和流暢的幀率。拾取電源時避免與紅點(diǎn)碰撞。此外,如果你想要那些額外的積分,就需要不停向前移動
地址:http://swooop.playcanvas.com/類型:街機(jī)
在一個美麗多彩的3D世界里,到處飛翔,收集寶石和星星。
地址:http://www.freeriderhd.com/
Free Rider HD 是一款令人上癮的游戲,你可以在其他玩家繪制的賽道上騎自行車。可以在成千上萬的播放器曲目中選擇一個播放,也可以創(chuàng)建自己的曲目并分享。
地址:http://entanglement.gopherwoodstudios.com/zh-CN-index.html類型:拼圖,娛樂
這個游戲的目的是通過在網(wǎng)格上放置線段來創(chuàng)建一條盡可能長的路徑。你可以單獨(dú)玩,也可以和朋友一起玩。
地址:https://www.modern.ie/en-us/ie6countdown#escape-from-xp
類型:動作,街機(jī)
用“Escape from XP”來慶祝 Windows XP 的終結(jié)。你的任務(wù)是拯救最后一個陷入Clippy暴政的開發(fā)人員。
地址:http://polycraftgame.com/類型:角色扮演,塔防,動作
在這個很棒的3D游戲中,你到處收集資源,建造東西,完成任務(wù)。關(guān)于它的所有東西都經(jīng)過拋光,并且運(yùn)行也非常順暢。
地址:https://gabrielecirulli.github.io/2048/類型:拼圖
一個非常上癮的游戲,你可能已經(jīng)玩過了。在 2048 ,你移動編號的圖塊并合并它們。當(dāng)界面中最大數(shù)字是`2048 時,游戲勝利。
地址:http://arcade.lostdecadegames.com/onslaught_arena/
類型:動作
一種快節(jié)奏的復(fù)古生存游戲,您可以使用不同的武器與成群的敵人作戰(zhàn)。
地址:http://chrome.angrybirds.com/類型:游戲
《憤怒的小鳥》游戲,這就不用介紹了。
地址:https://www.cubeslam.com/mcycrs
類型:街機(jī),多人
具有豐富的色彩和炫酷的3D圖形乒乓球游戲。我們可以通過向朋友發(fā)送一個URL來挑戰(zhàn)他們,還可以通過網(wǎng)絡(luò)攝像頭看到對方。
地址:http://hypnoticowl.com/games/the-wizard/類型:動作,角色扮演,策略
Wizard 是基于回合的地牢爬行者,在里面會遇到神話般的怪物并找到奇妙的咒語。該游戲具有酷炫的戰(zhàn)斗機(jī)制,有時可能會帶來很大挑戰(zhàn)。
地址:http://phoboslab.org/xtype/類型:動作,街機(jī)
在這款酷炫的太空射擊游戲中,你目的就是要起戰(zhàn)勝 Boss。
地址:http://orteil.dashnet.org/cookieclicker/類型:休閑,搞笑
Cookie clicker 是一款可能為了開玩笑而創(chuàng)建的游戲,但仍然提供了大量的樂趣。你可以從0個cookie開始,然后單擊一些有效率的cookie,最后你可能會發(fā)現(xiàn)自己擁有數(shù)十億個cookie。
地址:http://play.elevatorsaga.com/類型:拼圖,編碼
這類屬于程序員類型游戲 。在電梯中的任務(wù)是通過對電梯的運(yùn)動進(jìn)行編程,以最有效的方式運(yùn)送人員,這些都是用 JavaScript 來完成的。
地址:http://gameofbombs.com/landing類型:動作,角色扮演,多人
Game of Bombs是一個轟炸機(jī)類型的游戲,在廣闊地圖上,都有著敵方玩家。收集力量,皮膚和成就,以成為最佳轟炸機(jī)玩家的方式。
地址:http://or.paleozoic.com/類型:平臺游戲,動作
Olympia Rising具有漂亮復(fù)古外觀圖形的游戲。它坐落在古希臘,在那里我們扮演的女人被賦予了重新的機(jī)會,所以我們的任務(wù)就是逃離死者的世界。
地址: https://ned.im/pixel-race-game/類型:街機(jī),賽車
Pixel Race是一款簡單概念概念,你可以在收集硬幣的同時控制汽車以避開障礙物。如果有足夠的耐心和空閑時間,那么你可能會打破記錄(記錄為36309個硬幣)。
地址:https://littlealchemy.com/類型:拼圖
從這四個基本元素開始,將它們組合起來,創(chuàng)建510種可能的組合。
地址:http://www.kevs3d.co.uk/dev/arena5/類型:街機(jī)
在數(shù)字領(lǐng)域中飛行并射擊幾何敵人以獲得高分。
地址:https://vector-runner-remix.tresensa.com/
類型:街機(jī)
在這個充滿色彩和幾何形狀的平臺游戲中,盡你所能奔跑吧。
地址:http://playbiolab.com/類型:動作
一款出色的像素藝術(shù)平臺游戲,你必須在這里逃脫充滿突變生物和其他不良生物的實(shí)驗(yàn)室。
地址:http://worldsbiggestpacman.com/#類型:街機(jī)
地址:http://games.jessefreeman.com/new-super-resident-raver/
從即將到來的僵尸入侵中拯救驚慌失措的人們。收集錢,升級你的武器和戰(zhàn)斗僵尸。
作者:Danny Markov 來源:tutorialzin 譯者:前端小智
原文:https://tutorialzine.com/2015/02/30-amazing-games-made-only-with-html5
想實(shí)現(xiàn)2048游戲書寫代碼時可以分為三個步驟
先書寫HTML把游戲結(jié)構(gòu)搭建出來
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<!--最外部的大框-->
<div class="outermost"> //包裹游戲全局的大盒子
<!--title-->
<span class="top"><b>SCORE:<span id="score01"></span></b></span>//頂部實(shí)時顯示的游戲分?jǐn)?shù)
<!--游戲大框框-->
<div class="big">//2048游戲?yàn)樗男兴牧幸虼诵枰?6個div盒子
<div class="cell" id="c00"></div>
<div class="cell" id="c01"></div>
<div class="cell" id="c02"></div>
<div class="cell" id="c03"></div>
<div class="cell" id="c10"></div>
<div class="cell" id="c11"></div>
<div class="cell" id="c12"></div>
<div class="cell" id="c13"></div>
<div class="cell" id="c20"></div>
<div class="cell" id="c21"></div>
<div class="cell" id="c22"></div>
<div class="cell" id="c23"></div>
<div class="cell" id="c30"></div>
<div class="cell" id="c31"></div>
<div class="cell" id="c32"></div>
<div class="cell" id="c33"></div>
//游戲結(jié)束時會彈出的提示框
</div>
<!--提示框-->
<div class="tips" id="gameover">
<p>GAME OVER!!! <br>
SCORE: <span id="score02">0</span><br>
<button class="startbtn">重新開始</button>
</p>
</div>
<!--重玩一遍-->
<div class="foot">
<button class="replay"><a>重玩一遍</a></button>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
經(jīng)過了第一步的搭建游戲框架,第二部就是給游戲添加樣式,使它能顯示出來
*{
padding: 0px;
margin: 0px auto;
font-family: Arial;
}
/*最外部的大框*/
.outermost{
width: 480px;
height: 600px;
font-size: 40px;
margin-top: 120px;
}
/*title*/
<!--頂部顯示分?jǐn)?shù)的樣式-->
.top{
margin: auto;
}
.top span{
color: red;
}
/*游戲大框框*/
.big{
width: 480px;
height: 480px;
background:pink;
border-radius: 8px;
}
<!--給每一個盒子包裹的小框子添加樣式-->
.cell{
list-style: none;
float: left;
display: inline-block;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
background-color: #fbf8cd;
margin-left: 16px;
margin-top: 16px;
border-radius: 6px;
}
<!--提前把出現(xiàn)的數(shù)2、4、8、16等的數(shù)所在的格子給添加好樣式增加游戲體驗(yàn)感-->
.n2{background-color:#f65e3b;color:#776e65}
.n4{background-color:#33b5e5;color:#776e65}
.n8{background-color:#f2b179;color:#776e65}
.n16{background-color:#f59563;color:#776e65}
.n32{background-color:#f67c5f;color:#776e65}
.n64{background-color:#f65e3b;color:#776e65}
.n128{background-color:#edcf72;color:#776e65}
.n256{background-color:#edcc61;color:#776e65}
.n512{background-color:#9c0;color:#776e65}
.n1024{background-color:#33b5e5;color:#776e65;font-size:40px}
.n2048{background-color:#09c;color:#776e65;font-size:40px}
/*提示框樣式*/
.tips{
border: 1px solid #cccccc;
background: #FFFFFF;
width: 400px;
height: 200px;
border-radius: 10px;
color: #ff4456;
text-align: center;
line-height: 60px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -200px;
margin-top: -100px;
display: none;
}
.tips .startbtn{
height: 50px;
width: 200px;
color: #FFFFFF;
font-size: 20px;
line-height: 50px;
border-radius: 10px;
background: cornflowerblue;
border: none;
}
/*重玩一遍*/
.foot{
width: 200px;
height: 50px;
}
.foot>.replay{
width: 200px;
height: 50px;
background: aquamarine;
margin-top: 60px;
color: lightpink;
border:0;
font-size: 24px;
font-weight: bold;
border-radius: 6px;
}
書寫好了HTML+CSS部分游戲的模樣也就出來了,如下圖所示:
下面就到了最后也是最關(guān)鍵的一步----添加行為,也就是JS部分的書寫,給其添加效果
//創(chuàng)建一個對象,里面存儲所有的游戲數(shù)據(jù)及游戲方法
var game = {
data : [], //定義一個數(shù)組,用來存所有的游戲的數(shù)據(jù)
score : 0, //定義一個分?jǐn)?shù)的屬性
gamerunning : 1, //定義一個游戲運(yùn)行的狀態(tài),將其設(shè)置為1與其他狀態(tài)區(qū)分開
gameover : 0, //定義一個游戲結(jié)束的狀態(tài)
status : 0, //這個是目前游戲的狀態(tài),時刻的跟上面兩個狀態(tài)做比較,確定游戲處于運(yùn)行或者結(jié)束
start : function(){ //游戲開始時候的方法
// 游戲開始的時候肯定是要把游戲的狀態(tài)設(shè)置成游戲運(yùn)行的狀態(tài)
// this == game
this.status = this.gamerunning;
// 游戲開始的時候分?jǐn)?shù)清空
this.score = 0;
// 數(shù)組中的所有元素全部設(shè)置成0
this.data = [
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0]
];
this.randomNum();//調(diào)用下面自定義的隨機(jī)函數(shù),可以在移動和開始的時候隨機(jī)出來一個數(shù)
this.randomNum();//調(diào)用兩次是因?yàn)檫@是游戲開始時的方法,開局隨機(jī)出現(xiàn)兩個數(shù)和位置,因此需要調(diào)用兩次
this.dataView();//調(diào)用下面所寫的更新視圖的方法
},
// 隨機(jī)數(shù)的函數(shù),開始的時候隨機(jī)生成,移動的時候隨機(jī)生成
randomNum: function(){
while(true){
// 隨機(jī)生成行和列 0 - 3隨機(jī)整數(shù)
var r = Math.floor( Math.random() * 4 ); //隨機(jī)生成一個行
var c = Math.floor( Math.random() * 4 ); //隨機(jī)生成一個列
if(this.data[r][c] == 0){
var num = Math.random() > 0.5 ? 2 : 4;//隨機(jī)出現(xiàn)2或4
this.data[r][c] = num;
break;
}
}
},
// 更新試圖的方法
dataView: function(){
// 大的循環(huán),然后把所有的元素全部遍歷一遍
for(var r = 0; r < 4; r++){
for(var c = 0; c < 4; c++){
// 找到對應(yīng)的div
var div = document.getElementById("c" + r + c); //字符串拼接
if(this.data[r][c] != 0){
// 數(shù)組中對應(yīng)的內(nèi)容放到格子上面去
div.innerHTML = this.data[r][c];
// 樣式也寫成對應(yīng)的
div.className = "cell n" + this.data[r][c];
}else{
div.innerHTML = "";
div.className = "cell"
}
}
}
// 更新分?jǐn)?shù)
document.getElementById("score01").innerHTML = this.score;
//游戲沒有結(jié)束的時候 彈出層時刻都是隱藏的
if(this.status == this.gamerunning){
document.getElementById("gameover").style.display = "none";
}else{
document.getElementById("gameover").style.display = "block";
document.getElementById("score02").innerHTML = this.score;
}
},
// 判斷游戲是否結(jié)束的方法
isgameover: function(){
for(var r = 0; r < 4; r++){
for(var c = 0; c < 4; c++){
if(this.data[r][c] == 0){ //里面有空格子的時候,游戲還是可以運(yùn)行
return false; //表示游戲還沒有結(jié)束
}
if(c < 3){//判斷左右是否有相同的
if(this.data[r][c] == this.data[r][c+1]){
return false;
}
}
if(r < 3){
if(this.data[r][c] == this.data[r+1][c]){
return false;
}
}
}
}
return true;
},
//移動的方法,左 右 上 下四個部分
// 左 右 上 下
// 左移的方法
moveLeft: function(){
var before = String(this.data); //之前做一次轉(zhuǎn)換
// 具體的移動需要處理的邏輯,直接處理好每一行即可
for(var r = 0;r < 4;r ++){
this.moveLeftInRow(r);
}
var after = String(this.data); //移動之后再做一次轉(zhuǎn)換
// 如果說移動之前不等于移動之后,肯定是發(fā)生了移動
if(before != after){
this.randomNum(); //生成隨機(jī)數(shù)
// 生成的隨機(jī)數(shù)可能會造成游戲的gameover
if(this.isgameover()){
// 改變游戲的狀態(tài)
this.status = this.gameover
}
// 更新視圖
this.dataView();
}
},
moveLeftInRow: function(r){ //只去做處理每一行的邏輯
for(var c = 0; c < 3; c++){
var nextc = this.getNextinRow(r,c);
if(nextc != -1){
if(this.data[r][c] == 0){
// 如果等于0,直接替換
this.data[r][c] = this.data[r][nextc];
this.data[r][nextc] = 0; //位置恢復(fù)成0
c --; //要讓位置恢復(fù)到原地
}else if(this.data[r][c] == this.data[r][nextc]){
this.data[r][c] *= 2; //位置直接翻一倍
this.data[r][nextc] = 0;
this.score += this.data[r][c]; //更新分?jǐn)?shù)
}
}else{ //沒有找到
break; //直接退出循環(huán)
}
}
},
getNextinRow: function(r,c){
for(var i = c + 1; i < 4; i++){
if(this.data[r][i] != 0){
return i; //表示已經(jīng)找到位置,并且把位置返回出來
}
}
return -1; //返回一個標(biāo)識符
},
// 右移的方法
moveRight: function(){
var before = String(this.data);
for(var r = 0; r < 4; r++){
this.moveRightInRow(r);
}
var after = String(this.data);
if(before != after){
this.randomNum();
if(this.isgameover()){
this.status = this.gameover;
}
this.dataView();
}
},
moveRightInRow: function(r){
for(var c = 4; c > 0; c--){
var prevc = this.getPrevInRow(r,c);
if(prevc != -1){
if(this.data[r][c] == 0){
this.data[r][c] = this.data[r][prevc];
this.data[r][prevc] = 0;
c ++
}else if(this.data[r][c] == this.data[r][prevc]){
this.data[r][c] *= 2;
this.data[r][prevc] = 0;
this.score += this.data[r][c];
}
}else{
break;
}
}
},
getPrevInRow: function(r,c){
for(var i = c - 1; i >= 0; i--){
if(this.data[r][i] != 0){
return i;
}
}
return -1;
},
// 上移
moveUp: function(){
var before = String(this.data);
for(var c = 0; c < 4; c++){
this.moveUpInCol(c);
}
var after = String(this.data);
if(before != after){
this.randomNum();
if(this.isgameover()){
this.status = this.gameover;
}
this.dataView();
}
},
moveUpInCol: function(c){
for(var r = 0;r < 4; r++){
var nextr = this.getNextInCol(r,c);
if(nextr != -1){
if(this.data[r][c] == 0){
this.data[r][c] = this.data[nextr][c];
this.data[nextr][c] = 0;
r -- ;
}else if(this.data[r][c] == this.data[nextr][c]){
this.data[r][c] *= 2;
this.data[nextr][c] = 0;
this.score += this.data[r][c];
}
}else{
break;
}
}
},
getNextInCol: function(r,c){
for(var i = r + 1; i < 4; i++){
if(this.data[i][c] != 0){
return i;
}
}
return -1;
},
// 下移的方法
moveDown: function(){
var before = String(this.data);
for(var c = 0;c < 4; c++){
this.moveDownInCol(c);
}
var after = String(this.data);
if(before != after){
this.randomNum();
if(this.isgameover()){
this.status = this.gameover;
}
this.dataView();
}
},
moveDownInCol: function(c){
for(var r = 3; r > 0; r--){
var prev = this.getPrevIncol(r,c);
if(prev != -1){
if(this.data[r][c] == 0){
this.data[r][c] = this.data[prev][c];
this.data[prev][c] = 0;
r -- ;
}else if(this.data[r][c] == this.data[prev][c]){
this.data[r][c] *= 2;
this.data[prev][c] = 0;
this.score += this.data[r][c];
}
}else{
break;
}
}
},
getPrevIncol: function(r,c){
for(var i = r - 1; i >= 0; i--){
if(this.data[i][c] != 0){
return i;
}
}
return -1;
},
}
game.start();
console.log(game.data)
console.log(game.status);
console.log(game.score);
//鍵盤事件
document.onkeydown = function(){
if(event.keyCode == 37){
//console.log("左")
game.moveLeft();
}else if(event.keyCode == 38){
//console.log("上")
game.moveUp()
}else if(event.keyCode == 39){
//console.log("右")
game.moveRight()
}else if(event.keyCode == 40){
//console.log("下")
game.moveDown()
}
}
//touch事件
//手指按下
var startX;//設(shè)定開始起始位置的x坐標(biāo)
var startY;//設(shè)定開始起始位置的y坐標(biāo)
var endX;//設(shè)定結(jié)束滑動位置的x坐標(biāo)
var endY;//設(shè)定結(jié)束滑動位置的y坐標(biāo)
document.addEventListener('touchstart',function(){
// console.log("手指按下了屏幕")
console.log(event);
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
})
//手指移動
//document.addEventListener('touchmove',function(){
// console.log("手指的移動")
//})
//手指松開
document.addEventListener("touchend",function(){
// console.log("手指松開")
console.log(event);
endX = event.changedTouches[0].pageX;//如何獲取結(jié)束時的位置x
endY = event.changedTouches[0].pageY;
var X = endX - startX;
var Y = endY - startY
var absX = Math.abs(X) > Math.abs(Y);
var absY = Math.abs(Y) > Math.abs(X);
if(X > 0 && absX){
console.log("右滑動")
game.moveRight()
}else if(X < 0 && absX){
console.log("左滑動")
game.moveLeft()
}if(Y > 0 && absY){
console.log("下滑動")
game.moveDown()
}if(Y < 0 && absY){
console.log("上滑動")
game.moveUp()
}
})
就這樣一個簡單的2048游戲就完成啦~
非常感謝您能看到這里~
關(guān)注我~帶給你更多驚喜~
一方面想對git底層工作原理有更多理解,另外觀摩下Linus大神的編碼思路和風(fēng)格
git項(xiàng)目地址:https://github.com/git/git.git
開源庫隨著功能增加代碼越來越龐大,并且主干思想也會被越來越多的分支細(xì)節(jié)所淹沒,所以直接選擇git第一個版本代碼進(jìn)行參考。時間回到2005年4月7號的下午。
commit e83c5163316f89bfbde7d9ab23ca2e25604af290 (HEAD)
Author: Linus Torvalds <torvalds@ppc970.osdl.org>
Date: Thu Apr 7 15:13:13 2005 -0700
進(jìn)到目錄里看一下,只有幾個文件,總共代碼行才1000出頭
review+debug是學(xué)習(xí)代碼庫非常有效的方法,所以先讓代碼跑起來。mac上嘗試編譯,出現(xiàn)一些警告以及錯誤。本地做一些修改后編譯通過。有同學(xué)要自己嘗試動手,可以參照以下修改:
1、安裝openssl庫以及zlib庫
brew install openssl
brew install zlib
2、修改編譯以及鏈接選項(xiàng)并指定頭文件以及庫位置同時關(guān)閉棄用函數(shù)報警
CFLAGS=-g -Wno-deprecated -I/usr/local/opt/openssl/include/
LDFLAGS=-L/usr/local/opt/openssl/lib/ -L/usr/local/opt/zlib/lib/
3、鏈接庫修改,從 -lssl 改為 -lcrypto -lz
4、main函數(shù)增加返回值、修改時間相關(guān)結(jié)構(gòu)體
5、m1芯片mac沒有找到可用gdb版本,可以使用lldb代替
以下是具體修改點(diǎn):
[graypig:]$ git diff
diff --git a/Makefile b/Makefile
index a6bba79ba1..fe779bdb75 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-CFLAGS=-g
+CFLAGS=-g -Wno-deprecated -I/usr/local/opt/openssl/include/
+LDFLAGS=-L/usr/local/opt/openssl/lib/ -L/usr/local/opt/zlib/lib/
CC=gcc
PROG=update-cache show-diff init-db write-tree read-tree commit-tree cat-file
@@ -8,27 +9,27 @@ all: $(PROG)
install: $(PROG)
install $(PROG) $(HOME)/bin/
-LIBS= -lssl
+LIBS= -lcrypto -lz
init-db: init-db.o
update-cache: update-cache.o read-cache.o
- $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
show-diff: show-diff.o read-cache.o
- $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
write-tree: write-tree.o read-cache.o
- $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
read-tree: read-tree.o read-cache.o
- $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
commit-tree: commit-tree.o read-cache.o
- $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
cat-file: cat-file.o read-cache.o
- $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
read-cache.o: cache.h
show-diff.o: cache.h
diff --git a/cache.h b/cache.h
index 98a32a9ad3..161a5aff90 100644
--- a/cache.h
+++ b/cache.h
@@ -12,6 +12,7 @@
#include <openssl/sha.h>
#include <zlib.h>
+#include <string.h>
/*
* Basic data structures for the directory cache
diff --git a/init-db.c b/init-db.c
index 25dc13fe10..d11b16bff5 100644
--- a/init-db.c
+++ b/init-db.c
@@ -19,8 +19,8 @@ int main(int argc, char **argv)
sha1_dir = getenv(DB_ENVIRONMENT);
if (sha1_dir) {
struct stat st;
- if (!stat(sha1_dir, &st) < 0 && S_ISDIR(st.st_mode))
- return;
+ if (!(stat(sha1_dir, &st) < 0) && S_ISDIR(st.st_mode))
+ return 0;
fprintf(stderr, "DB_ENVIRONMENT set to bad directory %s: ", sha1_dir);
}
diff --git a/show-diff.c b/show-diff.c
index b8522886a1..6d00ba2a6f 100644
--- a/show-diff.c
+++ b/show-diff.c
@@ -11,11 +11,11 @@ static int match_stat(struct cache_entry *ce, struct stat *st)
{
unsigned int changed = 0;
- if (ce->mtime.sec != (unsigned int)st->st_mtim.tv_sec ||
- ce->mtime.nsec != (unsigned int)st->st_mtim.tv_nsec)
+ if (ce->mtime.sec != (unsigned int)st->st_mtimespec.tv_sec ||
+ ce->mtime.nsec != (unsigned int)st->st_mtimespec.tv_nsec)
changed |= MTIME_CHANGED;
- if (ce->ctime.sec != (unsigned int)st->st_ctim.tv_sec ||
- ce->ctime.nsec != (unsigned int)st->st_ctim.tv_nsec)
+ if (ce->ctime.sec != (unsigned int)st->st_ctimespec.tv_sec ||
+ ce->ctime.nsec != (unsigned int)st->st_ctimespec.tv_nsec)
changed |= CTIME_CHANGED;
if (ce->st_uid != (unsigned int)st->st_uid ||
ce->st_gid != (unsigned int)st->st_gid)
diff --git a/update-cache.c b/update-cache.c
index 5085a5cb53..f9c8e0fc69 100644
--- a/update-cache.c
+++ b/update-cache.c
@@ -139,9 +139,9 @@ static int add_file_to_cache(char *path)
memset(ce, 0, size);
memcpy(ce->name, path, namelen);
ce->ctime.sec = st.st_ctime;
- ce->ctime.nsec = st.st_ctim.tv_nsec;
+ ce->ctime.nsec = st.st_ctimespec.tv_nsec;
ce->mtime.sec = st.st_mtime;
- ce->mtime.nsec = st.st_mtim.tv_nsec;
+ ce->mtime.nsec = st.st_mtimespec.tv_nsec;
ce->st_dev = st.st_dev;
ce->st_ino = st.st_ino;
ce->st_mode = st.st_mode;
核心邏輯:創(chuàng)建緩存目錄.dircache/objects,并且在此目錄下預(yù)創(chuàng)建256個目錄,命名規(guī)則
.dircache/objects/00 .dircache/objects/01 .dircache/objects/... .dircache/objects/ff
#include "cache.h"
int main(int argc, char **argv)
{
char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
int len, i, fd;
if (mkdir(".dircache", 0700) < 0) {
perror("unable to create .dircache");
exit(1);
}
/*
* If you want to, you can share the DB area with any number of branches.
* That has advantages: you can save space by sharing all the SHA1 objects.
* On the other hand, it might just make lookup slower and messier. You
* be the judge.
*/
sha1_dir = getenv(DB_ENVIRONMENT);
if (sha1_dir) {
struct stat st;
if (!stat(sha1_dir, &st) < 0 && S_ISDIR(st.st_mode))
return;
fprintf(stderr, "DB_ENVIRONMENT set to bad directory %s: ", sha1_dir);
}
/*
* The default case is to have a DB per managed directory.
*/
sha1_dir = DEFAULT_DB_ENVIRONMENT;
fprintf(stderr, "defaulting to private storage area\n");
len = strlen(sha1_dir);
if (mkdir(sha1_dir, 0700) < 0) {
if (errno != EEXIST) {
perror(sha1_dir);
exit(1);
}
}
//注意malloc申請內(nèi)存后不會清零,但是使用sprintf格式化會在末尾添加\0,所以不存在越界問題
path = malloc(len + 40);
memcpy(path, sha1_dir, len);
for (i = 0; i < 256; i++) {
//兩個16進(jìn)制字符格式打印
sprintf(path+len, "/%02x", i);
if (mkdir(path, 0700) < 0) {
if (errno != EEXIST) {
perror(path);
exit(1);
}
}
}
return 0;
}
緩存項(xiàng)設(shè)計(jì)經(jīng)過仔細(xì)考量,可以直接利用文件字節(jié)流還原內(nèi)存緩存項(xiàng)結(jié)構(gòu),省掉了拷貝動作。核心邏輯:
首先讀取.dircache/index的文件內(nèi)容,對要加入緩存的文件進(jìn)行校驗(yàn)后,進(jìn)行zlib壓縮并計(jì)算sha1值,按照sha1計(jì)算文件的路徑.dircache/objects/xx/xx{19}, 保存文件然后更新全局cache信息,并將全局cache保存到磁盤上生成新的.dircache/index。
文件內(nèi)容索引文件格式:"blob " + size + null + zlib壓縮后的文件內(nèi)容。
int main(int argc, char **argv)
{
int i, newfd, entries;
entries = read_cache();
if (entries < 0) {
perror("cache corrupted");
return -1;
}
newfd = open(".dircache/index.lock", O_RDWR | O_CREAT | O_EXCL, 0600);
if (newfd < 0) {
perror("unable to create new cachefile");
return -1;
}
for (i = 1 ; i < argc; i++) {
char *path = argv[i];
// 判斷路徑是否合法,排除: . .. //結(jié)尾
if (!verify_path(path)) {
fprintf(stderr, "Ignoring path %s\n", argv[i]);
continue;
}
if (add_file_to_cache(path)) {
fprintf(stderr, "Unable to add %s to database\n", path);
goto out;
}
}
if (!write_cache(newfd, active_cache, active_nr) && !rename(".dircache/index.lock", ".dircache/index"))
return 0;
out:
unlink(".dircache/index.lock");
}
read_cache讀取緩存邏輯:打開緩存文件.dircache/index,通過mmap將文件映射到內(nèi)存,校驗(yàn)文件sha1,根據(jù)頭enty個數(shù)還原緩存數(shù)據(jù)。
int read_cache(void)
{
int fd, i;
struct stat st;
unsigned long size, offset;
void *map;
struct cache_header *hdr;
errno = EBUSY;
if (active_cache)
return error("more than one cachefile");
errno = ENOENT;
sha1_file_directory = getenv(DB_ENVIRONMENT);
if (!sha1_file_directory)
sha1_file_directory = DEFAULT_DB_ENVIRONMENT;
if (access(sha1_file_directory, X_OK) < 0)
return error("no access to SHA1 file directory");
fd = open(".dircache/index", O_RDONLY);
if (fd < 0)
return (errno == ENOENT) ? 0 : error("open failed");
map = (void *)-1;
if (!fstat(fd, &st)) {
map = NULL;
size = st.st_size;
errno = EINVAL;
if (size > sizeof(struct cache_header))
map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
}
close(fd);
if (-1 == (int)(long)map)
return error("mmap failed");
hdr = map;
if (verify_hdr(hdr, size) < 0)
goto unmap;
// 根據(jù)緩存數(shù)量來申請內(nèi)存,預(yù)留1.5倍空間
active_nr = hdr->entries;
active_alloc = alloc_nr(active_nr);
active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
// 通過文件字節(jié)直接還原內(nèi)存結(jié)構(gòu)
offset = sizeof(*hdr);
for (i = 0; i < hdr->entries; i++) {
struct cache_entry *ce = map + offset;
offset = offset + ce_size(ce);
active_cache[i] = ce;
}
return active_nr;
unmap:
munmap(map, size);
errno = EINVAL;
return error("verify header failed");
}
verify_hdr校驗(yàn)緩存頭:通過緩存重新計(jì)算sha1,跟緩存頭sha1對比進(jìn)行校驗(yàn)
static int verify_hdr(struct cache_header *hdr, unsigned long size)
{
SHA_CTX c;
unsigned char sha1[20];
// 基礎(chǔ)校驗(yàn), 簽名&版本
if (hdr->signature != CACHE_SIGNATURE)
return error("bad signature");
if (hdr->version != 1)
return error("bad version");
SHA1_Init(&c);
// 提取緩存頭中除了sha1部分的數(shù)據(jù)
SHA1_Update(&c, hdr, offsetof(struct cache_header, sha1));
// 提取緩存內(nèi)容數(shù)據(jù),hdr+1是指跳過緩存頭
SHA1_Update(&c, hdr+1, size - sizeof(*hdr));
// 計(jì)算sha1
SHA1_Final(sha1, &c);
// 對比sha1
if (memcmp(sha1, hdr->sha1, 20))
return error("bad header sha1");
return 0;
}
特殊宏函數(shù)說明:
struct cache_entry {
struct cache_time ctime;
struct cache_time mtime;
unsigned int st_dev;
unsigned int st_ino;
unsigned int st_mode;
unsigned int st_uid;
unsigned int st_gid;
unsigned int st_size;
unsigned char sha1[20];
unsigned short namelen;
// 0長度字符數(shù)組,并不占用空間
unsigned char name[0];
};
// 計(jì)算緩存項(xiàng)的長度
#define ce_size(ce) cache_entry_size((ce)->namelen)
/*
offsetof(struct cache_entry,name)獲取name在結(jié)構(gòu)體中的偏移,即除去name之外的緩存項(xiàng)目大小
& ~7 將最低3位置0,也就是說將最終的長度對8對齊
+8 為了防止將最低3位置0后大小變小,因此提前+8來預(yù)留空間
*/
#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
獲取文件meta信息,給文件建立索引,將文件加入緩存entry.
static int add_file_to_cache(char *path)
{
int size, namelen;
struct cache_entry *ce;
struct stat st;
int fd;
fd = open(path, O_RDONLY);
if (fd < 0) {
if (errno == ENOENT)
return remove_file_from_cache(path);
return -1;
}
if (fstat(fd, &st) < 0) {
close(fd);
return -1;
}
namelen = strlen(path);
size = cache_entry_size(namelen);
ce = malloc(size);
memset(ce, 0, size);
memcpy(ce->name, path, namelen);
ce->ctime.sec = st.st_ctime;
ce->ctime.nsec = st.st_ctimespec.tv_nsec;
ce->mtime.sec = st.st_mtime;
ce->mtime.nsec = st.st_mtimespec.tv_nsec;
ce->st_dev = st.st_dev;
ce->st_ino = st.st_ino;
ce->st_mode = st.st_mode;
ce->st_uid = st.st_uid;
ce->st_gid = st.st_gid;
ce->st_size = st.st_size;
ce->namelen = namelen;
if (index_fd(path, namelen, ce, fd, &st) < 0)
return -1;
return add_cache_entry(ce);
}
文件建索引流程:將文件mmap到內(nèi)存,使用zlib壓縮meta信息(blob size + null byte), 壓縮文件內(nèi)容,計(jì)算sha1,根據(jù)sha1計(jì)算緩存文件名,寫入緩存文件。
static int index_fd(const char *path, int namelen, struct cache_entry *ce, int fd, struct stat *st)
{
z_stream stream;
int max_out_bytes = namelen + st->st_size + 200;
void *out = malloc(max_out_bytes);
void *metadata = malloc(namelen + 200);
void *in = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0);
SHA_CTX c;
close(fd);
if (!out || (int)(long)in == -1)
return -1;
memset(&stream, 0, sizeof(stream));
deflateInit(&stream, Z_BEST_COMPRESSION);
// 壓縮meta信息
/*
* ASCII size + nul byte
*/
stream.next_in = metadata;
stream.avail_in = 1+sprintf(metadata, "blob %lu", (unsigned long) st->st_size);
stream.next_out = out;
stream.avail_out = max_out_bytes;
while (deflate(&stream, 0) == Z_OK)
/* nothing */;
/*
* File content
*/
// 壓縮文件內(nèi)容
stream.next_in = in;
stream.avail_in = st->st_size;
while (deflate(&stream, Z_FINISH) == Z_OK)
/*nothing */;
deflateEnd(&stream);
SHA1_Init(&c);
SHA1_Update(&c, out, stream.total_out);
// 計(jì)算sha1
SHA1_Final(ce->sha1, &c);
// 文件內(nèi)容寫入緩存
return write_sha1_buffer(ce->sha1, out, stream.total_out);
}
int write_sha1_buffer(unsigned char *sha1, void *buf, unsigned int size)
{
char *filename = sha1_file_name(sha1);
int i, fd;
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0)
return (errno == EEXIST) ? 0 : -1;
write(fd, buf, size);
close(fd);
return 0;
}
根據(jù)哈希值計(jì)算文件名: 第一個哈希值決定目錄,剩余的19個哈希值決定文件名
/*
* NOTE! This returns a statically allocated buffer, so you have to be
* careful about using it. Do a "strdup()" if you need to save the
* filename.
*/
char *sha1_file_name(unsigned char *sha1)
{
int i;
static char *name, *base;
if (!base) {
char *sha1_file_directory = getenv(DB_ENVIRONMENT) ? : DEFAULT_DB_ENVIRONMENT;
int len = strlen(sha1_file_directory);
base = malloc(len + 60);
memcpy(base, sha1_file_directory, len);
memset(base+len, 0, 60);
// .dircache/objects/xx/xx{19}
base[len] = '/';
base[len+3] = '/';
name = base + len + 1;
}
for (i = 0; i < 20; i++) {
static char hex[] = "0123456789abcdef";
unsigned int val = sha1[i];
//根據(jù)哈希值計(jì)算文件名。第一個哈希值決定目錄,剩余的19個哈希值決定文件名
// i > 0是用來跳過"/", 第一個哈希值在"/"前,剩余的19個哈希值在"/"后
char *pos = name + i*2 + (i > 0);
*pos++ = hex[val >> 4];
*pos = hex[val & 0xf];
}
return base;
}
add_cache_entry將文件加入緩存: 緩存按照文件路徑排序,二分查找。
static int add_cache_entry(struct cache_entry *ce)
{
int pos;
pos = cache_name_pos(ce->name, ce->namelen);
/* existing match? Just replace it */
if (pos < 0) {
active_cache[-pos-1] = ce;
return 0;
}
/* Make sure the array is big enough .. */
if (active_nr == active_alloc) {
active_alloc = alloc_nr(active_alloc);
active_cache = realloc(active_cache, active_alloc * sizeof(struct cache_entry *));
}
/* Add it in.. */
active_nr++;
// 要插入的位置不在最后,從pos開始元素向后移動
if (active_nr > pos)
memmove(active_cache + pos + 1, active_cache + pos, (active_nr - pos - 1) * sizeof(ce));
active_cache[pos] = ce;
return 0;
}
cache_name_pos根據(jù)名字獲取緩存項(xiàng)的位置,二分查找。 這個函數(shù)返回值比較特殊, 沒有找到返回最后一次查找first(>0),找到了返回 -p -1(<0) 。這樣設(shè)計(jì),基于性能考慮,找到時返回了位置,在沒有找到的時候返回了要插入的位置。
static int cache_name_pos(const char *name, int namelen)
{
int first, last;
first = 0;
last = active_nr;
while (last > first) {
int next = (last + first) >> 1;
struct cache_entry *ce = active_cache[next];
int cmp = cache_name_compare(name, namelen, ce->name, ce->namelen);
if (!cmp)
return -next-1;
if (cmp < 0) {
last = next;
continue;
}
first = next+1;
}
return first;
}
cache_name_compare,先比較名稱,再比較長度, 0相等,-1 小于,1 大于
static int cache_name_compare(const char *name1, int len1, const char *name2, int len2)
{
int len = len1 < len2 ? len1 : len2;
int cmp;
cmp = memcmp(name1, name2, len);
if (cmp)
return cmp;
if (len1 < len2)
return -1;
if (len1 > len2)
return 1;
return 0;
}
核心邏輯:首先讀取緩存,針對緩存中的每個entry,根據(jù)meta判斷文件當(dāng)前是否有變更,如果有打印文件路徑以及sha1,并且根據(jù)sha1找到文件并解壓文件內(nèi)容,并調(diào)用系統(tǒng)的diff(diff -u - ${name})命令打印差異。 對diff命令,-代表標(biāo)準(zhǔn)輸入
int main(int argc, char **argv)
{
int entries = read_cache();
int i;
if (entries < 0) {
perror("read_cache");
exit(1);
}
for (i = 0; i < entries; i++) {
struct stat st;
struct cache_entry *ce = active_cache[i];
int n, changed;
unsigned int mode;
unsigned long size;
char type[20];
void *new;
if (stat(ce->name, &st) < 0) {
printf("%s: %s\n", ce->name, strerror(errno));
continue;
}
changed = match_stat(ce, &st);
if (!changed) {
printf("%s: ok\n", ce->name);
continue;
}
printf("%.*s: ", ce->namelen, ce->name);
for (n = 0; n < 20; n++)
printf("%02x", ce->sha1[n]);
printf("\n");
new = read_sha1_file(ce->sha1, type, &size);
show_differences(ce, &st, new, size);
free(new);
}
return 0;
}
show_differences 執(zhí)行系統(tǒng)命令diff打印差異
static void show_differences(struct cache_entry *ce, struct stat *cur,
void *old_contents, unsigned long long old_size)
{
static char cmd[1000];
FILE *f;
snprintf(cmd, sizeof(cmd), "diff -u - %s", ce->name);
f = popen(cmd, "w");
fwrite(old_contents, old_size, 1, f);
pclose(f);
}
核心邏輯:按照sha1計(jì)算緩存文件名,讀取文件解壓將內(nèi)容寫入臨時文件,并且打印類型以及長度
int main(int argc, char **argv)
{
unsigned char sha1[20];
char type[20];
void *buf;
unsigned long size;
char template[] = "temp_git_file_XXXXXX";
int fd;
if (argc != 2 || get_sha1_hex(argv[1], sha1))
usage("cat-file: cat-file <sha1>");
buf = read_sha1_file(sha1, type, &size);
if (!buf)
exit(1);
fd = mkstemp(template);
if (fd < 0)
usage("unable to create tempfile");
if (write(fd, buf, size) != size)
strcpy(type, "bad");
printf("%s: %s\n", template, type);
}
核心邏輯:讀取文件緩存數(shù)據(jù),組成樹內(nèi)容。內(nèi)容格式:tree size null mode name null [mode name null] mode name null。然后根據(jù)文件內(nèi)容計(jì)算sha1,根據(jù)sha1計(jì)算文件路徑,將壓縮后的數(shù)據(jù)寫入文件
int main(int argc, char **argv)
{
unsigned long size, offset, val;
int i, entries = read_cache();
char *buffer;
if (entries <= 0) {
fprintf(stderr, "No file-cache to create a tree of\n");
exit(1);
}
/* Guess at an initial size */
size = entries * 40 + 400;
buffer = malloc(size);
offset = ORIG_OFFSET;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = active_cache[i];
if (check_valid_sha1(ce->sha1) < 0)
exit(1);
// 空間不夠重新申請
if (offset + ce->namelen + 60 > size) {
size = alloc_nr(offset + ce->namelen + 60);
buffer = realloc(buffer, size);
}
// 格式:十進(jìn)制權(quán)限 文件名 NULL sha1
offset += sprintf(buffer + offset, "%o %s", ce->st_mode, ce->name);
buffer[offset++] = 0;
memcpy(buffer + offset, ce->sha1, 20);
offset += 20;
}
/*
offset - ORIG_OFFSET 數(shù)據(jù)長度
ORIG_OFFSET 數(shù)據(jù)偏移
將數(shù)據(jù)長度寫到預(yù)留空間的尾部,向前填入"tree ",并調(diào)整buffer offset位置
整體數(shù)據(jù)格式: tree size null mode name null sha1 [mode name null sha1] ... mode name null sha1
*/
i = prepend_integer(buffer, offset - ORIG_OFFSET, ORIG_OFFSET);
i -= 5;
memcpy(buffer+i, "tree ", 5);
buffer += i;
offset -= i;
write_sha1_file(buffer, offset);
return 0;
}
prepend_integer 從i個位置向前以字符串形式填寫val,并返回新的i
static int prepend_integer(char *buffer, unsigned val, int i)
{
buffer[--i] = '\0';
do {
buffer[--i] = '0' + (val % 10);
val /= 10;
} while (val);
return i;
}
數(shù)據(jù)樣例:
x buf x/60b buf
https://wenku.baidu.com/view/62a4aea6e63a580216fc700abb68a98271feacb0.html?_wkts_=1676432746759&bdQuery=lldb+%E8%BF%9E%E7%BB%AD%E5%86%85%E5%AD%98
基礎(chǔ)邏輯:校驗(yàn)參數(shù)后,獲取當(dāng)前登錄用戶的密碼相關(guān)信息,用來獲取用戶名、email,記錄changgelog。記錄當(dāng)前commit sha1,parent sha1 、author 、committer以及 評論信息,調(diào)整緩存頭"commit size." 根據(jù)文件內(nèi)容sha1計(jì)算文件名,并保存到object目錄。
int main(int argc, char **argv)
{
int i, len;
int parents = 0;
unsigned char tree_sha1[20];
unsigned char parent_sha1[MAXPARENT][20];
char *gecos, *realgecos;
char *email, realemail[1000];
char *date, *realdate;
char comment[1000];
struct passwd *pw;
time_t now;
char *buffer;
unsigned int size;
if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
usage("commit-tree <sha1> [-p <sha1>]* < changelog");
for (i = 2; i < argc; i += 2) {
char *a, *b;
a = argv[i]; b = argv[i+1];
if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))
usage("commit-tree <sha1> [-p <sha1>]* < changelog");
parents++;
}
if (!parents)
fprintf(stderr, "Committing initial tree %s\n", argv[1]);
// 讀取當(dāng)前用戶密碼信息,用來記錄changelog
pw = getpwuid(getuid());
if (!pw)
usage("You don't exist. Go away!");
realgecos = pw->pw_gecos;
len = strlen(pw->pw_name);
memcpy(realemail, pw->pw_name, len);
realemail[len] = '@';
gethostname(realemail+len+1, sizeof(realemail)-len-1);
time(&now);
realdate = ctime(&now);
gecos = getenv("COMMITTER_NAME") ? : realgecos;
email = getenv("COMMITTER_EMAIL") ? : realemail;
date = getenv("COMMITTER_DATE") ? : realdate;
remove_special(gecos); remove_special(realgecos);
remove_special(email); remove_special(realemail);
remove_special(date); remove_special(realdate);
init_buffer(&buffer, &size);
add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
/*
* NOTE! This ordering means that the same exact tree merged with a
* different order of parents will be a _different_ changeset even
* if everything else stays the same.
*/
for (i = 0; i < parents; i++)
add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
/* Person/date information */
add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);
add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", realgecos, realemail, realdate);
/* And add the comment */
while (fgets(comment, sizeof(comment), stdin) != NULL)
add_buffer(&buffer, &size, "%s", comment);
finish_buffer("commit ", &buffer, &size);
write_sha1_file(buffer, size);
return 0;
}
緩存處理邏輯:初始化了16K基本緩存大小,預(yù)留了40字節(jié)頭信息,每32k realloc一次內(nèi)存。代碼存在BUG,應(yīng)該是筆誤,16k 32k應(yīng)該設(shè)置成一樣大小,否則特殊場景會崩。
#define BLOCKING (1ul << 14)
#define ORIG_OFFSET (40)
/*
* Leave space at the beginning to insert the tag
* once we know how big things are.
*
* FIXME! Share the code with "write-tree.c"
*/
static void init_buffer(char **bufp, unsigned int *sizep)
{
char *buf = malloc(BLOCKING);
memset(buf, 0, ORIG_OFFSET);
*sizep = ORIG_OFFSET;
*bufp = buf;
}
static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
{
char one_line[2048];
va_list args;
int len;
unsigned long alloc, size, newsize;
char *buf;
va_start(args, fmt);
len = vsnprintf(one_line, sizeof(one_line), fmt, args);
va_end(args);
size = *sizep;
newsize = size + len;
alloc = (size + 32767) & ~32767;
buf = *bufp;
if (newsize > alloc) {
alloc = (newsize + 32767) & ~32767;
buf = realloc(buf, alloc);
*bufp = buf;
}
*sizep = newsize;
memcpy(buf + size, one_line, len);
}
設(shè)計(jì)巧妙,代碼簡潔工整,注重性能,注釋自由
git里兩個基本概念:The Object Database、Current Directory Cache
The Object Database:
對象數(shù)據(jù)庫,對象內(nèi)容采用zlib壓縮,對象名采用sha1,包含三類對象,BLOB(普通文件內(nèi)容)、TREE(文件權(quán)限/名稱/sha1集合,表示一次提交的內(nèi)容)、
CHANGESET(TREE父子鏈,表示變更歷史)。
Current Directory Cache:
git暫存區(qū),當(dāng)前緩存的文件的META信息
git第一版代碼保持了linux工具鏈風(fēng)格,每個工具只干一件事情,底層工具組合在一起完成代碼管理功能
1)update-cache:git add雛形,保存最新文件內(nèi)容到objects里,并更新本地目錄緩存
2)show-diff:git status雛形,實(shí)現(xiàn)了緩存中的文件與最新狀態(tài)差異對比
3)write-tree: git commit雛形1,保存工作區(qū)最新緩存樹到objects目錄并生成sha1
4)commit-tree: git commit雛形2,保存提交的樹的sha1以及的parent樹的sha1到object目錄并生成sha1
理解了這些工具實(shí)現(xiàn)邏輯,不難想象目前git的各種命令和概念的原理。比如分支,分支本質(zhì)只是一個changeset的sha1,基于sha1可以反向追溯每一次提交的tree。要實(shí)現(xiàn)兩次提交diff,對比兩個tree可以找到目錄差異以及變化的文件,基于文件的sha1可以找到文件進(jìn)而對比出文件的變化。分支拷貝,底層操作只需要拷貝一個sha1值,等等。
實(shí)現(xiàn)功能同時充分考量性能,緩存項(xiàng)頭格式設(shè)計(jì)、二分查找返回值的設(shè)計(jì)、文件內(nèi)容頭信息、文件訪問采用mmap避免內(nèi)核緩沖區(qū)到用戶緩沖區(qū)數(shù)據(jù)拷貝
雖然對常規(guī)業(yè)務(wù)來講,可讀性高于性能,但隨手可得的優(yōu)化是程序員基本素養(yǎng)
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。