gitlet.js - git在1k行的JavaScript中实现

2021-04-13 10:43:29

我写了Gitlet来展示Git如何在封面下工作。我写了很可读。我很大地评论了代码。

如果您不熟悉基本Git命令,则可以以六百字(下图)中的阅读。

对于六千个单词深入潜入地下室内部,你可以从informout中加入git。

想象一下,您有一个名为Alpha的目录。它包含一个包含文件文本的FileCalled Number.txt。

您运行git add number.txt以将number.txt添加到索引。 TheIndex是Git跟踪的所有文件的列表。它是文件的文件名到文件的内容。它现在有映射号.txt - >第一的。运行add命令也添加了包含首先到git对象数据库的ablob对象。

首先运行git commit -m。这有三件事。首先,它会在对象数据库中进行树对象。此对象代表字母表顶级中的项目列表。此对象具有指向第一个BLOB ObjectThat的指针,当您ran git添加时创建。其次,它创建了代表您刚刚提交的存储库的版本的acmmit对象。这包括指向TreeObject的指针。第三,它指向新ComputObject的主分支。

你运行git clone。 ../beta。这会创建一个名为beta的新目录。它将其初始化为Git存储库。它将Alpha对象数据库中的TheObjects复制到Beta ObjectsDatabase。它在Commit ObjectThat在Alpha存储库上的Master分支点处指向Beta上的Master分支。它索引索引索引镜像第一个提交的内容。对您的文件进行itus-number.txt - 镜像索引。

您移动到Beta存储库。您将Number.txt的内容更改为秒。您运行git add number.txt和gitcommit -m。创建的提交对象具有其父父级的porterto。 Commit命令点在第二个提交时分支机构分支。

您返回Alpha存储库。你运行git远程添加beta ../ beta。这将beta存储库设置为远程存储库。

在封面下,这运行了Git fetch beta master。这会找到第二个提交的主机,并将它们从Betarepository复制到Alpha存储库。它在第二个提交对象中指向Alpha的eta主机的记录。它更新fetch_headto表示从Betarepository获取主分支。

在封面下,拉出命令运行git mergefetch_head。这是读取的fetch_head,这表明Beta存储库上的MasterBranch是最近的FetchedBranch。它获得了Alpha的Beta'Smaster记录指向的提交对象。这是第二个提交。 Master Branchon Alpha指向第一个提交,这是第二次提交的祖先。这意味着要完成合并,Themerge命令只能指向第二个Commit的主分支。 Merge命令更新索引以镜像第二项提交的内容。它更新了镜像映射的工作副本。

你运行git branch红色。这将在第二个提交对象中创建一个名为红色的分支。

你运行git checkout红色。在结账之前,头部指向主分支。它现在指向红色分支。这使得该分支电流分支。

您将Number.txt的内容设置为第三,运行git addnumbers.txt并运行git commit -m第三。

你运行git push beta红色。这会发现第三个消息的对象,并将它们从Alpha存储库复制到Betarepository。它指向TheThird Commit对象的Beta存储库上的红色分支,就是这样。

如果--bare已通过,请写入Git Config,指示存储库裸露。如果没有通过--bare,写入git config说存储库不是裸露的。

config:config.objtostr({core:{"" {bare:opts.bare === true}}),对象:{},refs:{heads:{},}};

使用gitletstructure js对象写标准git目录结构。如果存储库不裸露,请将目录放在.gitlet目录中。如果有的话,则将它们放在其中的顶层。

if(已添加到文件.Length === 0){抛出新错误(files.pathfromreporoot(path)+"与任何文件不匹配);

}否则if(filestorm.length === 0){抛出新错误(files.pathfromreporoot(path)+"与任何文件不匹配);

}如果是(fs.existssync(path)&& fs.statsync(path).isdirectory()&&!opts.r){抛出新错误("未删除" +路径+"递归没有-r"); } 别的 {

获取要删除的所有文件的列表,并在磁盘上更改了Alsobeen。如果此列表不为空,则中止。

var changeStorm = Util.Intersection(Diff.AddedOrmodifiedFiles(),FileStorm); if(changeStorm.length> 0){抛出新错误("这些文件有更改:\ n" + changeastorm.join(" \ n")+" \ n& #34;);

否则,删除匹配路径的文件。删除磁盘并从索引中删除。

} else {filestorm.map(files.workingcopypath).filter(fs.existssync).foreach(fs.unlinksync); filestorm.foreach(函数(p){gitlet.update_index(p,{demove:true});}); }}},

commit()创建一个提交的对象,表示索引的当前中心,在提交时向对象attical和point头写入对象。

将树对象的哈希与TheWas的顶部的哈希进行比较,只需用头部提交点的树对象的哈希写入。如果他们是一样的,中止就没有新的事情。

if(refs.hash(" head")!== undefined&& treehash ===对象.treehash(objects.read(refs.hash(" head" head"))) ){抛出新的错误("#34; + headdesc +" \ nnothing致力于提交,工作目录清洁"); } 别的 {

var conflictpaths = index.conflickspats(); if(merge.ismergeinprogress()&& conflictpaths.length> 0){抛出新的错误(ConflictPaths.map(函数(p){return" u" + p;})。加入( " \ n")+" \ ncannot提交,因为你有未被用的文件\ n");

如果存储库处于合并状态,请使用预编写的提交消息。如果存储库不在主题状态,请使用带有-m的消息。

如果存在merge_head,则存储库处于最新状态。删除merge_head和merge_msgto退出Meremblate。报告合并完成。

}别的{返回" [" +头顶+" " + Commithash +"]" + m; }}}},

if(name ===未定义){return object.keys(refs.localheads())。映射(函数(分支){return(branch === refs.headbranchname()?" *": "")+分支;})。加入(" \ n")+" \ n&#34 ;;

头部没有指向一个提交,所以没有新的分支指出。中止。如果存储库没有提交,这最可能是最可能的。

}否则如果(refs.hash(" head")===未定义){抛出新错误(refs.heftbranchname()+"不是有效的对象名称");

}否则如果(refs.exist(refs.tolocalref(姓名))){抛出新错误("一个名为" +名称+&#34的分支;已经存在"); 否则,通过创建名为名称的新文件来创建新分支,该文件包含头指向点的提交的散列。 checkout()更改索引,工作副本和头部Toreflect的内容。 REF可能是分支名称或acmmit哈希。 if(!Objects.exists(Tohash)){抛出新错误(Ref +"与Gitlet&#34所知的任何文件不匹配; 中止如果哈希要签出指向Anot Anit的对象。 }否则if(对象.type(objects.read(tohash))!=="提交"){抛出新错误("引用不是树:" + ref); 中止如果ref是当前检查出的分支的名称。如果head被分离,则参考是在该哈希上的提交哈希和头部is点。

}否则如果(ref === refs.headbranchname()|| ref === files.read(files.gitletpath("头部")))){返回"已经开启" + ref; } 别的 {

获取工作副本中更改的文件列表。获取头部提交和TheComit中不同的文件的列表以签出。如果任何文件都显示在两个列表中。

var paths = diff.changedfilescommitwollwollite(tohash); if(paths.length> 0){抛出新错误("本地变更将丢失\ n" + paths.join(" \ n")+" \ n& #34;);

如果ref在对象目录中,则必须是一个备用,因此此结帐已拆下头部。

获取当前提交和提交签出之间的差异列表。将它们写入工作副本。

写下提交被检查到头部。如果头部被分离,则提交哈希是直接写的,是头文件。如果头部未被分离,则被检查出来的Branch被写入头部。

返回isdetachinghead? "注意:检查" + tohash +" \ nyou是独立的头国家。" :"切换到分支" + ref; }}},

if(ref1!==未定义&& refs.hash(ref1)===未定义){抛出新错误("含糊不清的参数" + ref1 +&#34 ;:未知修订版" );

}否则如果(ref2!==未定义&& refs.hash(ref2)===未定义){抛出新的错误(" ambiguous论点" + ref2 +&#34 ;:未知修订版&# 34;);

Gitlet仅显示每个已更改的文件的名称,并且是否已添加,修改或删除。为简单起见,未显示TheChanged内容。

差异发生在两个版本的存储库之间。 refirst版本是ref1解析为索引或索引的哈希。第二个版本是扫描到或工作副本的哈希值。

return object.key.keys(nametostatus).map(函数(路径){return nameTostatus [path] +"" +路径;}).join(" \ n")+&# 34; \ n&#34 ;; }},

中止如果命令不是“添加”。仅支持“添加”。

}否则if(name in config.read()["远程"]){抛出新错误(" remote" +名称+"已经存在");

fetch()记录分支在Remote上的提交。它不会更改本地分支。

}否则如果(!(远程in config.read()。远程)){抛出新错误(远程+"似乎不是git存储库"); } 别的 {

转到远程存储库并获取Committhat分支的哈希值。

注意下,提交的散列,此存储库当前查询远程分支已开启。

获取远程对象目录中的所有对象并跟踪它们。到本地对象目录。 (这是让所有对象所需的所有物体都是局面的占态度的方式,提交远程分支是开启的。)

将文件的内容设置为.gitlet / refs / remotes / [远程] / [branch]到newhash,远程分支正在开启的提交的HASH。

录制远程分支on fetch_head的提交的哈希。 (用户可以调用gitlet mergefetch_head来合并分支Intotheir本地分支的远程版本。有关更多详细信息,请参阅Gitlet.Merge()。)

返回["来自" + RemoteURL," COUNT" + remoteObjects.Length,Branch +" - > " +遥控+" /" +分支+(merge.isaforcefetch(Oldhash,Newhash)?"(强制)":"")。加入(" \ n")+& #34; \ n&#34 ;; }}},

merge()查找当前校验的分支的提交之间的差异以及转换为的提交。它发现或创建一个提交的提交,将其应用于已被检查的分支。

中止如果ref未解析为哈希值,或者如果该散列是一个提交对象的哈丁。

}否则如果(giverhash === undefined || objects.type(objects.read(giverhash))!=="提交"){抛出新错误(REF +&#34 ;:预期提交类型&# 34;);

如果目前的分支 - 接收者 - 已经合并了 - 已经拥有给予者的变化。如果接收者和智能者是相同的提交,或者给予者是接收器的祖先,这就是这种情况。

获取工作副本中更改的文件列表。获取接收者和Giver中不同的文件的列表。 ifany文件在两个列表中出现然后中止。

var paths = diff.cangedfilescommitwuldwollite(giverhash); if(paths.length> 0){抛出新错误("本地变更将丢失\ n" + paths.join(" \ n")+" \ n& #34;);

如果接收者是给予者的祖先,则执行快速的转发。这是可能的,因为已经存在所有给予者的改变进入治疗方案。

快速转发手段使当前的分支反映了Giverhash点的通知。该分支是Pointedat GiverHash。该索引设置为匹配GiverHash点的提交的内容。工作副本ISSET以匹配该提交的内容。

如果接收方不是adviver的祖先,则必须创建一个mergecommit。

存储库被投入合并状态。将Merge_Head文件写入并将其内容设置为GiverHash。编写Merge_msg文件,并将其设置为Boilerplate合并提交消息。创建了Amerge Diff,这将使Receiver的内容变为给予者的内容。这包含每个文件的路径,这是不同的,无论是删除,删除或修改,还是在冲突中。添加了FileSare添加到索引和工作副本。从索引和工作副本中删除了areremoved文件。修改文件中修改在索引和工作副本中。 isin冲突的文件写入工作副本以包含Tereceiver和Giver版本。接收器和GiverVersions都写入索引。

如果有任何冲突文件,则显示一条消息,即用户必须在合并才能完成之前对其进行调整。

如果没有冲突文件,则从合并的更改中创建提交,合并结束。

pull()获取分支在远程处于remote的提交。提交到当前分支的合并。

push()获取在远程提交的远程上的本地回购点分支中分支的提交。

}否则如果(!(远程in config.read()。远程)){抛出新错误(远程+"似乎不是git存储库"); } else {var remotepath = config.read()。远程[遥控] .URL; var remotecall = util.onremote(Remotepath);

if(remotecall(refs.ischeckedout,分支)){抛出新的错误("拒绝更新检查分支和#34; +分支); } 别的 {

如果远程分支 - 接收者 - HasalReady纳入Giverhash Pointsto的提交,请不要​​做任何。如果接收者提交和赠送管理器是相同的,或者如果adver提交是接收者提交的祖先,这就是这种情况。

中止如果远程上的分支不能快速转发,则提交给予者的指向。如果接收者提交是秘密提交的祖先,则完成快进CONONLY。

}否则如果(!opts.f&&!merge.canfastword(receiverhash,giverhash)){抛出新错误("未能推动一些refs" + remotepath);

设置本地repo的Commit Branch是onat remote到Giverhash的记录(因为它是Nowis的)。

返回["" + Remotepath," Count" + Objects.AllObjects()。长度,分支+" - > " +分支] .join(" \ n")+" \ n&#34 ;; }}}},

status()报告repo的状态:当前分支,未触发的文件,冲突文件,暂整的文件和未暂存的文件。

if(remotepath ===未定义|| targetpath ===未定义){抛出新错误("您必须指定远程路径和目标路径");

}否则如果(!fs.existssync(remotepath)||!util.onremote(remotepath)(files.inrepo)){抛出新错误("存储库" + remotepath +"不存在& #34;);

}否则如果(fs.existssync(fs.existsync;& fs.readdirsync(targetpath).length> 0){抛出新错误(目标路径+"已经存在,不是空");

如果远程repo具有任何提交,则将存在该散列。新的存储库记录传递分支在遥控器上的提交。然后它将Master on thalew存储库设置为该提交的点。

Update_index()将文件的内容添加到theIndex的路径上,或从索引中删除文件。

update_index:函数(路径,opts){files.assertinrepo(); config.assertnotbare(); 选择= opts || {}; var pathfromroot = files.pathfromreporoot(路径); var isondisk = fs.existssync(路径); var isinindex = index.hasfile(路径,0); if(isondisk&& fs.statsync(path).isdirectory()){抛出新错误(pathfromroot +"是一个目录 - 在\ n&#34内添加文件; }否则如果(opts.remove&& isondisk&& isinindex){ 中止如果文件被删除并在冲突中。 Gitletdoes不支持这个。 如果正在删除文件,则不在磁盘上且处于TheIndex中,从索引中删除它。 如果正在删除文件,则不在磁盘上而不是索引,没有工作要做。 中止如果文件位于磁盘上而不是索引中,而且未通过。 }否则如果(!opts.add&&&&&& isinindex){抛出新的错误("不能添加" + pathfromroot +"到索引 - 使用 - add选项 \ n");

如果文件位于磁盘上,则传递-add或文件是索引,则将文件的当前内容添加到索引中。

}否则如果(!opts.remove&& isondisk){抛出新的错误(路径辐射+"不存在和 - rovove未通过\ n"); }},

write_tree()获取索引的内容,并存储一个treeObject,表示对象目录的该内容。

update_ref()获取ReftoupDateTeTopoints的提交的散列,并将ReftoupDate设置为相同的哈希值。

如果哈希指向对象目录中的对象是非提交的,则中止。

}否则if(对象.type(objects.read(哈希))!=="提交"){var branch = refs.terminalref(Reftoupdate);抛出新的错误(分支+"不能指非提交对象" +哈希+" \ n");

refs是提交哈希的名称。 ref是文件的名称.SOME REFS表示本地分支,如refs / head / master或refs / head / feats。有些代表远程分支,如refs / remotes / horiz / master。有些代表存储库的重要状态,如头部,merge_head和fetch_head。 reffiles包含哈希或其他参考。

ISREF:功能(REF){RETURN REF!==未定义&& (ref.match(" ^ refs / heads / [a-za-z - ] + $")|| ref.match(" ^ refs / remotes / [a-za-z - ] + / [a-za-z - ] + $")|| ["头&#34 ;," fetch_head&#34 ;," merge_head"]。索引(参考)!== - 1); },

如果ref是“头”,头部指向一个分支,请返回branch。

否则,假设ref是一个不合格的本地参考(如掌握),并将其转换为合格的参考(如refs / heads / master)

哈希:函数(Reforhash){if(对象.Exists(Reforhash)){Return Reforhash; }否则{var terminalref = refs.terminalref(Reforhash); if(terminalRef ===" fetch_head"){return refs.fetchheadbranchtomerge(refs.heftbranchname()); }否则如果(refs.exists(terminalref)){return files.read(files.gitletpath(eriverlerref)); }}},

IsHeadDetached()如果head包含Commithash,而不是分支的REF,则返回True。

ischeckedout()如果存储库不裸露并且头部指向名为branch的分支,则返回true

fetchheadbranchtomerge()读取fetch_head文件,并留下远程BranchName指向的哈希哈希。有关fetch_head的更多信息,请参阅gitlet.fetch()。

fetchhedbranchtomerge:函数(branchname){returnuture.lines(files.read(files.gitletpath(" fetch_head")).filter(函数(l) ......