编程实现简单的git版本管理系统

编程实现简单的git版本管理系统

git在写作业,开发软件项目,特别是团队合作开发,迭代开发中有着很大的用处。为了更进一步理解git的原理,以及掌握git的用法,了解git的实现机制,这里根据git系统的功能,实现了下git的代码。

下面先实现一个简化版的git(名为GeetFS)

给他文件系统(GeetFS)

题目背景

自从被安利了版本控制软件Git,顿顿沉迷其中无法自拔。恰好最近学习了文件系统的一些知识,于是顿顿打算在办签证的途中实现一个类似于Git的特殊“文件系统”GeetFS。

基本布局

普通的文件系统使用存储设备保存用户文件,但是GeetFS不是一个普通的文件系统。
——顿顿

GeetFS是一个基于内存的文件系统,其使用内存中的结构来保存和管理用户的文件数据。图2展示了GeetFS的工作流程和GeetFS保存数据的基本方式。在使用GeetFS时,用户发送请求给GeetFS。GeetFS会解析用户的请求,在内存数据结构中进行操作,完成用户请求。

GeetFS中的一些基本概念

  • 文件:与普通文件系统中的文件概念相同,GeetFS中的文件可以保存数据。GeetFS中的文件支持读取、写入、删除。写入一个不存在(或此前已被删除)的文件会自动创建该文件;
  • 文件名:每个文件拥有一个文件名,为一个长度不超过128的字符串。文件名的内容仅包含大小写英文字母(A ~ Z和a ~ z)和数字(0 ~ 9)。顿顿认为GeetFS的用户都非常善良,所以他们能够保证每个GeetFS命令中的文件名均满足上述规定,GeetFS在实现的时候无需进行检查。如图2中的file1表示一个名为file1的文件。
  • 暗文件:为了表示文件的删除,GeetFS中引入了暗文件的概念。对于一个名为filename的文件,其暗文件名称为‐filename(即在文件名前增加了一个负号‐)。由于在用户创建的文件名中不允许出现‐,暗文件的文件名与普通文件的文件名并不会混淆。暗文件的文件大小为0,其中不可保存数据。其仅表示名为filename的文件被删除。一个文件与其暗文件不应同时出现在同一个提交中,也不应同时出现在暂存区中(关于提交和暂存区的概念请看下面两条)。如图2中的‐file2为一个暗文件,表示对文件file2的删除。
  • 暂存区:用户所有的修改,包括文件写入、删除等,在未提交时,均保存在暂存区(即图2中的uncommitted结构,包括虚线椭圆及其右侧的文件)。其中文件的写入(包括创建)以文件的形式保存,而文件的删除以暗文件的形式保存。
  • 提交:与在Git中类似,一个提交表示GeetFS的一个历史状态。一般来说,提交由commit命令创建,GeetFS将当前暂存区中的所有修改保存下来,并赋予一个唯一的提交名,成为一个提交。提交名的命名要求与文件名相同,且无需进行格式检查。除了commit命令外,用户还可以通过merge命令创建一个提交。通过merge命令创建的提交将两个现有提交进行合并。提交的创建在后文中有具体描述。除了GeetFS中的第一个提交不存在父提交之外,其余每个提交拥有一个父提交(由commit命令创建)或者两个父提交(由merge命令创建)。如图2中的cmt1表示一个名为cmt1的提交。
  • HEAD:与Git中类似,HEAD表示当前的头部,指向头部提交。用户当前能够访问哪些文件,以及文件的内容,取决于当前缓存区中的内容以及当前头部所指向的提交。
  • GeetFS命令:用户使用GeetFS命令对GeetFS的内容进行操作。命令只能通过标准输入传递给GeetFS,除了write命令占据两行之外,其他GeetFS均只占一行(即以换行符结尾)。

编程实现简单的git版本管理系统

GeetFS的存储结构

GeetFS中保存了一个头部,一个暂存区结构,和若干个提交结构。

  • 头部(HEAD):GeetFS中有且仅有一个头部,为指向当前头部提交的一个指针或者引用,当GeetFS中不存在任何提交时,头部为空。
  • 暂存区结构(uncommitted):GeetFS中有且仅有一个暂存区结构。暂存区结构中包含了所有还未提交的修改。在此结构中,应该保存所有被修改(包括创建)文件的名称、大小和内容。对于删除的文件,该结构中应当保存对应的暗文件的信息。注意暗文件的大小为0,不保存数据,因此只有暗文件的文件名是有意义的信息。
  • 提交结构:GeetFS中可以有零个或者多个提交结构,保存在元数据结构之中。暂存区结构中的内容在提交后,变为提交结构。一旦生成,提交结构中的内容是不可修改的。

初始状态

一个刚刚被创建出来的GeetFS文件系统只包括一个空头部和一个空的暂存区结构(即其中没有任何文件或暗文件)。

基本操作

普通的文件系统使用目录组织文件,但是GeetFS不是一个普通的文件系统。
——顿顿

作为一个“与(zhu)众(yao)不(shi)同(lan)”的文件系统,GeetFS只支持对文件的操作,并不支持目录(文件夹),同时GeekFS支持的文件操作只有三个:写入、读取和删除。这三个操作均依赖于文件的查找。

文件的查找

查找一个文件X的流程如图所示,具体规则如下:1.如果暂存区中能够查找到X文件,则找到了该文件;2.如果暂存区中不存在X文件,但是存在其暗文件‐X,则表示目标文件被删除,返回文件已被删除;3.如果在暂存区中,既不存在X文件,也
不存在其暗文件,则在暂存区中无法确定是否存在该文件,需继续查找头部所指向的提交。如果头部为空,则文件不存在;4.在一个提交中查找文件的方法与在暂存区中查找的方法相同,即先后检查目标文件和其暗文件。如果该提交中依然无法确定是否存在该文件,则继续查找该提交的父提交,直至能够确定目标文件的存在性,或父提交不存在,则该文件不存在。对于具有两个父提交的提交,其查找方法在后文merge命令中给出。

编程实现简单的git版本管理系统

写入命令

共包括两行输入,其中第一行格式为write <filename> <offset> <len>(其中尖括号表示参数,下同),表示向文件<filename>中写入数据,写入的数据长度为<len>个字节,写入的开始位置为文件的<offset>位置(注意,在文件的位置0写
入1个字符,写入的是文件的第1个字符)。如果<offset>的位置超出了该文件的大小,则从文件当前末尾到<offset>之间的区域使用ASCII字符点(.)进行填充。在此行命令之后的一行,用户会输入<len>个字节作为文件内容,(注意结尾会有
一个换行符\n,不算做文件内容)。用户输入的内容中不包含换行字符,但是可能包含空格。在执行写入命令时,GeetFS首先按照前述方法查找该文件,并根据不同情况进行不同操作:

  • 如果在暂存区中该文件被找到,则直接进行写入操作;
  • 如果在某个提交中找到了该文件,则先将找到的文件拷贝到暂存区中(即在暂存区中创建该文件,并将找到的文件的内容拷贝到新文件中),再在暂存区中的文件中进行写入操作;
  • 如果查找结果为文件被删除,如果是在暂存区中被删除,则删除暂存区中对应的暗文件,并在暂存区中创建该文件后进行写入操作;
  • 如果是在某个提交中被删除,则在暂存区中创建该文件后进行写入操作;
  • 如果该文件不存在,则在暂存区中创建该文件,并进行写入操作。

写入命令不产生输出。

读取命令

共占一行,格式为read <filename> <offset> <len>,表示从文件<filename>的<offset>位置开始读取此后的<len>个字节。对于超出文件当前大小的部分,每个超出的字节以一个ASCII字符点(.)替代。在执行时,GeetFS首先按前述方法查找该文件,如果能找到文件,则输出文件中对应的内容;如果文件不存在或者文件被删除,则输出<len>个ASCII字符点(.)。文件读取命令的输出共占一行,因此在文件内容后应有换行(\n)字符,格式也可以参考样例。

删除命令

占一行,格式为unlink <filename>,表示删除名为<filename>的文件。如果GeetFS中无法找到该文件或该文件被删除,则什么都不做。如果能够找到该文件,则在暂存区中添加该文件的暗文件。如果目标文件是在暂存区中被找到的,则还需要从暂存区中删除目标文件,只保留其暗文件。删除命令不产生输出。注意,删除操作并不能抵消文件创建操作的效果。在文件X不存在的情况下,用户可以首先创建文件X,之后将文件X删除。在删除后,暂存区中会存有一个X的暗文件(‐X),与文件X被创建前的状态不同。

列举命令

占一行,格式为ls,输出在当前的暂存区和当前的头部状态下,用户能够读到的文件(即可以查找到的文件,不包括暗文件)个数,以及其中按字典序排列,名字最小的文件名和名字最大的文件名。文件个数和两个文件名之间以一个空格隔开,共占一行。如果用户能读到的文件数为0,则只需输出数字0,占一行,无需给出文件名。列举命令的输出共占一行,因此在列举内容之后应有换行(\n)字符,具体可以参考样例。

高级操作

普通的文件系统仅仅是一个文件系统,但是GeetFS不是一个普通的文件系统。
——顿顿

作为一个有Git情节的文件系统,GeetFS当然不仅仅支持标准的文件系统接口。其还支持一系列与Git操作相似的高级命令。

提交命令(commit)

占一行,格式为commit <cmtname>。
commit命令将暂存区中的修改进行提交,其接受一个字符串类型参数<cmtname>,为新提交的名称。在进行提交时,GeetFS将当前的暂存区uncommitted重命名为给定的提交名称,并更新元数据信息。新的提交(<cmtname>)的父提交为此时头部所指向的提交。如果此时头部为空,则新提交没有父提交。此后,GeetFS将更新头部,让其指向刚刚创建的新提交(<cmtname>)。最后,GeetFS还会创建一个新的空暂存区,用于保存此后的修改。
注意,如果在提交时暂存区为空,或名为<cmtname>的提交已经存在,则该命令执行失败,GeetFS中不产生任何修改。提交命令不产生任何输出。

切换命令(checkout)

占一行,格式为checkout <cmtname>。
checkout接受一个参数,为提交名<cmtname>。该命令将当前的头部指向<cmtname >。在支持该命令之后,提交之间的关系可能会“分叉”。checkout命令不一定会成功。若在进行checkout时,暂存区不为空,或者名为<cmtname>的提交不存在,则checkout命令执行失败,GeetFS中不应产生任何修改。

合并命令(merge)

占一行,格式为merge <mergee> <cmtname>。
merge命令接受两个参数,分别为需要合并的提交名<mergee>和新提交的名字<cmtname>。假设此时头部指向的提交为headcmt,该命令将<mergee>中的内容合并到提交headcmt之上。具体来说,GeetFS会创建一个新的提交,名为<cmtname>,其两个父提交为headcmt和mergee。由merge命令创建的提交中不包含任何文件和数据,只记录了两个父提交,表示这两个父提交的内容在逻辑上进行了合并。
注意,如果在进行merge时满足以下任何一个条件,则merge执行失败,GeetFS中不产生任何修改。

  • 失败条件1:暂存区不为空;
  • 失败条件2:<mergee>与headcmt为同一个提交;
  • 失败条件3:名为<mergee>的提交不存在。

支持merge命令会影响文件查找的规则:在支持merge命令后,一个提交(cmt)可以有两个不同的父提交(cmt.parent1和cmt.parent2)。在进行文件查找时,若在cmt中无法确定目标文件是否存在,则GeetFS需要通过cmt.parent1和cmt.parent2两
个父提交分别进行文件查找。

  • 若通过两个父提交均无法找到目标文件或其暗文件,则表示要查找的文件不存在;
  • 若仅能通过其中一个父提交找到该目标文件或其暗文件,则以此找到的文件作为文件查找的结果;
  • 若通过两个父提交均能找到该目标文件或其暗文件,则根据所找到的两个文件的所在提交的创建时间进行选择,取创建时间最近(最大)的文件作为文件查找的结果。如果所找到的两个文件为同一个文件(即在同一个提交中),则以此文件作为文件查找的结果。

输入格式

从标准输入读入数据。
输入的第一行为一个数字N,表示该测试中的命令总个数。从第二行开始,标准输入共包含N个命令。注意,每个写入命令write共占两行,其中第一行为写入命令以及其参数,第二行为需要写入的文件内容。文件内容中不会包含换行字符,但是可能会包含空格。其他每个命令占一行,具体格式在前文已经给出。命令均符合相应格式,文件名和提交名均符合规范,读写命令中的长度均大于0,因此无需对命令格式进行错误处理。

输出格式

输出到标准输出。
根据每个命令的要求进行相应输出。

样例

编程实现简单的git版本管理系统
编程实现简单的git版本管理系统
编程实现简单的git版本管理系统
编程实现简单的git版本管理系统

编程实现简单的git版本管理系统
编程实现简单的git版本管理系统
编程实现简单的git版本管理系统

文本格式样例输入

样例输入1

10
write file1 5 2
78
write file2 7 4
abcd
read file1 0 10
ls
read file2 4 10
unlink file2
ls
read file2 3 4
write file2 1 2
12
read file2 0 4

样例输出1

.....78...
2 file1 file2
...abcd...
1 file1 file1
....
.12.

样例输入2

22
write file1 3 2
ab
commit cmt1
write file2 2 4
cdef
read file1 0 10
ls
unlink file1
commit cmt2
ls
checkout cmt1
read file1 0 10
write file1 6 2
gh
write file3 2 3
ijk
commit cmt3
ls
checkout cmt2
ls
merge cmt3 cmt4
ls
read file3 0 10
checkout cmt3
write file3 5 3
lmn
read file3 0 10

样例输出2

...ab.....
2 file1 file2
1 file2 file2
...ab.....
2 file1 file3
1 file2 file2
3 file1 file3
..ijk.....
..ijklmn..

样例输入3

43
write file1 5 2
aa
write file2 2 2
bb
read file2 0 3
ls
commit cmt1
write file2 0 1
c
write file3 3 1
d
unlink file2
commit cmt2
read file3 0 5
commit cmt3
write file3 3 1
e
write file4 0 2
ff
commit cmt3
read file4 0 5
checkout cmt2
ls
unlink file4
unlink file2
read file1 0 5
unlink file1
ls
write file4 2 2
gg
write file1 3 2
hh
read file1 0 5
checkout cmt3
write file2 4 2
ii
unlink file1
commit cmt4
merge cmt4 cmt5
merge cmt3 cmt5
ls
read file4 0 5
unlink file4
merge cmt4 cmt6
write file1 8 1
j
commit cmt6
read file1 0 10
checkout cmt5
write file3 1 1
k
write file4 2 1
l
read file1 0 10
ls

样例输出3

..b
2 file1 file2
...d.
ff...
2 file1 file3
.....
1 file3 file3
...hh
3 file2 file4
..gg.
........j.
..........
3 file2 file4

子任务

编程实现简单的git版本管理系统
编程实现简单的git版本管理系统

提示

编程实现简单的git版本管理系统

完成代码

#include<stdio.h>
#include<map>
#include<string>
#include<string.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,a,b) for(int i=a;i>=b;--i)
using namespace std;

map<string,int> ms,mc;
int vis[21000],clo,tot,wrt,cmds,offset,len,sid,myfileid,nowworkspace,filename,lc[21000],lct,meid;
char s[1100],wr[2100000],ch,mes[1100];
char *temps;
string str,tos[21000],mins,maxs,mestr;
struct myfile{
    int len;
    char* s;
}myfiles[21000];
struct workspace{
    map<int,int>  tofile;
    int head,head2;
}workspaces[21000];
void write_file(int fileid){
    if (offset+len>myfiles[fileid].len){
        temps=new char[offset+len];
        memset(temps,'.',offset+len);
        for(int i=0;i<myfiles[fileid].len;++i)
            temps[i]=myfiles[fileid].s[i];
        if (myfiles[fileid].len>0)
            delete  myfiles[fileid].s;
        myfiles[fileid].len=offset+len;
        myfiles[fileid].s=temps;
    }
    for(int i=0;i<len;++i)
        myfiles[fileid].s[offset+i]=wr[i];
}
void cpyfile(int fromfile,int tofile){
    myfiles[tofile].len=myfiles[fromfile].len;
    if (myfiles[fromfile].len>0){
        myfiles[tofile].s=new char[myfiles[fromfile].len];
        memcpy(myfiles[tofile].s,myfiles[fromfile].s,myfiles[fromfile].len);
    }
}
void read_file(int fileid){
    int i;
    for(i=offset;i<offset+len&&i<myfiles[fileid].len;++i)
        putchar(myfiles[fileid].s[i]);
    for(;i<offset+len;++i)
        putchar('.');
    putchar('\n');
}
void unlink_file(int fileid){
    if (myfiles[fileid].len>0)
        delete myfiles[fileid].s;
    myfiles[fileid].len=-1;
}
void handle_write(){
    scanf("%s%d%d",s,&offset,&len);
    str=string(s);
    if  (!ms.count(str)){
        ms[str]=++sid;
        tos[sid]=str;
    }
    filename=ms[str];

    getchar();
    wrt=0;
    while ((ch=getchar())>30) wr[wrt++]=ch;
    
    if (workspaces[nowworkspace].tofile.count(filename)){
        write_file(workspaces[nowworkspace].tofile[filename]);
    }else{
        // faworkspace=workspaces[nowworkspace].head;
        // while (faworkspace){
        //     if (workspaces[faworkspace].tofile.count(filename)){
        //         workspaces[nowworkspace].tofile[filename]=++myfileid;
        //         cpyfile(workspaces[faworkspace].tofile[filename],myfileid);
        //         write_file(myfileid);
        //         return;
        //     }
        //     faworkspace=workspaces[faworkspace].head;
        // }
        clo++;
        vis[workspaces[nowworkspace].head]=clo;
        vis[workspaces[nowworkspace].head2]=clo;
        fd(i,nowworkspace-1,1) if (vis[i]==clo){
            vis[workspaces[i].head]=clo;
            vis[workspaces[i].head2]=clo;
            if (workspaces[i].tofile.count(filename)){
                workspaces[nowworkspace].tofile[filename]=++myfileid;
                cpyfile(workspaces[i].tofile[filename],myfileid);
                write_file(myfileid);
                return;
            }
        }
        workspaces[nowworkspace].tofile[filename]=++myfileid;
        write_file(myfileid);
    }
}
void handle_read(){
    scanf("%s%d%d",s,&offset,&len);
    str=string(s);
    if  (!ms.count(str)){
        for(int i=0;i<len;++i) putchar('.');
        putchar('\n');
        return;
    }
    filename=ms[str];

    if (workspaces[nowworkspace].tofile.count(filename)){
       read_file(workspaces[nowworkspace].tofile[filename]);
    }else{
        // faworkspace=workspaces[nowworkspace].head;
        // while (faworkspace){
        //     if (workspaces[faworkspace].tofile.count(filename)){
        //         read_file(workspaces[faworkspace].tofile[filename]);
        //         return;
        //     }
        //     faworkspace=workspaces[faworkspace].head;
        // }
        clo++;
        vis[workspaces[nowworkspace].head]=clo;
        vis[workspaces[nowworkspace].head2]=clo;
        fd(i,nowworkspace-1,1) if (vis[i]==clo){
            vis[workspaces[i].head]=clo;
            vis[workspaces[i].head2]=clo;
            if (workspaces[i].tofile.count(filename)){
                read_file(workspaces[i].tofile[filename]);
                return;
            }
        }       
        read_file(0);
    }
}
void handle_unlink(){
    scanf("%s",s);
    str=string(s);
    if  (!ms.count(str)){
        return;
    }
    filename=ms[str];   

     if (workspaces[nowworkspace].tofile.count(filename)){
       unlink_file(workspaces[nowworkspace].tofile[filename]);
    }else{
        // faworkspace=workspaces[nowworkspace].head;
        // while (faworkspace){
        //     if (workspaces[faworkspace].tofile.count(filename)){
        //         if (myfiles[workspaces[faworkspace].tofile[filename]].len>0){
        //             workspaces[nowworkspace].tofile[filename]=++myfileid;
        //             myfiles[myfileid].len=-1;
        //         }
        //         return;
        //     }
        //     faworkspace=workspaces[faworkspace].head;
        // }
        clo++;
        vis[workspaces[nowworkspace].head]=clo;
        vis[workspaces[nowworkspace].head2]=clo;
        fd(i,nowworkspace-1,1) if (vis[i]==clo){
            vis[workspaces[i].head]=clo;
            vis[workspaces[i].head2]=clo;
            if (workspaces[i].tofile.count(filename)){
                if (myfiles[workspaces[i].tofile[filename]].len>0){
                    workspaces[nowworkspace].tofile[filename]=++myfileid;
                    myfiles[myfileid].len=-1;
                }
                return;
            }
        }       
    }   
}
void handle_ls(){
    map<int,int> tempms;
    lct=0;
    // faworkspace=nowworkspace;
    // while (faworkspace){
    //     lc[++lct]=faworkspace;
    //     faworkspace=workspaces[faworkspace].head;
    // }
    clo++;
    vis[nowworkspace]=clo;
    fd(i,nowworkspace,1) if (vis[i]==clo){
        vis[workspaces[i].head]=clo;
        vis[workspaces[i].head2]=clo;
        lc[++lct]=i;
    }       
    fd(i,lct,1){
        for(auto it=workspaces[lc[i]].tofile.begin();it!=workspaces[lc[i]].tofile.end();++it)
            tempms[it->first]=it->second;
    }

    tot=0;
    for(auto it=tempms.begin();it!=tempms.end();++it){
        if (myfiles[it->second].len>0){
            if (!tot||tos[it->first]<mins) mins=tos[it->first];
            if (!tot||tos[it->first]>maxs) maxs=tos[it->first];
            ++tot;
        }
    }
    if (tot) 
        printf("%d %s %s\n",tot,mins.c_str(),maxs.c_str());
    else
        printf("0\n");
}
void handle_commit(){
    scanf("%s",s);
    if (!workspaces[nowworkspace].tofile.size())
        return;
    str=string(s);
    if  (mc.count(str))
        return;
    mc[str]=nowworkspace;
    ++nowworkspace;
    workspaces[nowworkspace].head=nowworkspace-1;
    workspaces[nowworkspace].tofile.clear();
}
void handle_checkout(){
    scanf("%s",s);
    if (workspaces[nowworkspace].tofile.size())
        return;
    str=string(s);
    if  (!mc.count(str))
        return;
    // nowworkspace=mc[str];
    workspaces[nowworkspace].head=mc[str];
    workspaces[nowworkspace].tofile.clear();
}
void handle_merge(){
    scanf("%s%s",mes,s);
    if (workspaces[nowworkspace].tofile.size())
        return;
    mestr=string(mes);
    if (!mc.count(mestr))
        return;
    meid=mc[mestr];
    if (meid==workspaces[nowworkspace].head)
        return;
    
    str=string(s);
    if (mc.count(str)) return;//???
    workspaces[nowworkspace].head2=meid;
    mc[str]=nowworkspace;
    ++nowworkspace;
    workspaces[nowworkspace].head=nowworkspace-1;
    workspaces[nowworkspace].tofile.clear();
}
int main(){
    nowworkspace=1;

    scanf("%d",&cmds);
    while (cmds--){
        scanf("%s",s);
        if (s[0]=='w'&&s[1]=='r'){//&&s[2]=='i'&&s[3]=='t'&&s[4]=='e'
           handle_write();
           continue;
        }
        if (s[0]=='r'&&s[1]=='e'){//&&s[2]=='a'&&s[3]=='d'
            handle_read();
            continue;
        }
        if (s[0]=='u'&&s[1]=='n'){//&&s[2]=='l'&&s[3]=='i'&&s[4]=='n'&&s[5]=='k'
            handle_unlink();
            continue;
        }
        if (s[0]=='l'&&s[1]=='s'){
            handle_ls();
            continue;
        }
        if (s[0]=='c'&&s[1]=='o'){
            handle_commit();
            continue;
        }
        if (s[0]=='c'&&s[1]=='h'){
            handle_checkout();
            continue;
        }
        if (s[0]=='m'&&s[1]=='e'){
            handle_merge();
            continue;
        }
    }
    return 0;
}
上一篇:三,Geoserver矢量数据仓库(/datastores)


下一篇:Scss的使用场景