JGit源码研习(一)——git ObjectDirectory的结构
JGit源码研读(一)——git ObjectDirectory的结构
Git是一个有趣的版本控制,git的一些初步知识可以参照http://www.open-open.com/lib/view/open1328069609436.html
Git的java版是jgit,具体实现是egit,是eclipse的一个插件,但是jgit是一个纯粹的用java实现的Git,具体网址参考http://www.eclipse.org/jgit
Git这么强大,究竟强大在何处.
按照本人的理解,git存储是依靠一个叫做\.git\objects\ 的目录,在objects下面,存储了每次发生了变化的文件,然后git通过\.git\index 这个文件来索引那些发生了变化又被记录下来的文件.
git的原则是,只要加入了版本控制的文件发生了任何变化,都会在objects这个目录中做一个快照,说白了就是复制一份当前版本的文件,然后通过索引控制这个文件是属于哪个版本的.
也就是这个原因,对一个大文件多次修改提交版本,会造成\.git目录越来越庞大.
不过git在保存文件的时候也做了一些压缩的操作.
查看jgit中这个类:org.eclipse.jgit.storage.file.ObjectDirectoryInserter
如下:
Jgit中有一个叫org.eclipse.jgit.lib.Repository的类,它是贯穿整个Git系统的线索,其用法:
在objects目录中,有很多名字类似:fd36ba1c6ca79657d06bdc57511161fc03fae9这样的文件,其实这些文件的命名是通过sha-1算法算出来的,具体在
ObjectId org.eclipse.jgit.lib.ObjectInserter.idFor(int type, byte[] data, int off, int len)中实现这样的算法
上面的类有两个重要的参数type和data,data就是文件的内容,type包含OBJ_COMMIT、OBJ_TREE、OBJ_BLOB、OBJ_TAG这四种类型,tree代表了提交的目录,tag是指出当前Object的一些信息。blod就是文件,其实就是真正被版本跟踪的文件。commit是指代一次commit操作,有了这几个参数,git把commit、tree、tag、blod这些object(在objects目录里面,以上四个操作都存储为object)区分开。
也就是说被版本跟踪的文件,只要发生那怕一个字节的变化,都会产生不同的sha-1码,这个时候,git就会到他的objects目录里面查找有没有这个文件(根据sha-1),没有则创建。具体代码看
ObjectDirectory的insert方法
最后把这个临时文件压入db中,也就是文件系统,objects目录下。
另外,/.git目录下面还有个index文件,这个文件记录是索引objects中已经存在的文件,把我们真正被跟踪的文件与存放在objects目录中的文件链接起来(被跟踪的文件路径-objects中的sha-1文件名)
/.git目录下有个 HEAD文件,内容是告诉git当前指向哪个分支,\.git\refs\heads\ 目录里面的文件就是各个分支,以及他的sha-1码,这个sha-1会与objects中一个文件相对应.在每产生一个版本,例如:commit这些操作都会引起\.git\refs\heads\ HEAD所指的分支的内容改变.也会产生一个新的sha-1名字的文件在objects中,这将会在我第二篇关于git文章进行详细的分析.
checkout和switch bracnch的操作都会引起HEAD的内容改变.
\.git\logs\refs\heads\中的内容是记录版本的记录,以方便回滚版本,这里的记录都是每\.git\refs\heads\个分支文件的sha-1改变记录.
Git是一个有趣的版本控制,git的一些初步知识可以参照http://www.open-open.com/lib/view/open1328069609436.html
Git的java版是jgit,具体实现是egit,是eclipse的一个插件,但是jgit是一个纯粹的用java实现的Git,具体网址参考http://www.eclipse.org/jgit
Git这么强大,究竟强大在何处.
按照本人的理解,git存储是依靠一个叫做\.git\objects\ 的目录,在objects下面,存储了每次发生了变化的文件,然后git通过\.git\index 这个文件来索引那些发生了变化又被记录下来的文件.
git的原则是,只要加入了版本控制的文件发生了任何变化,都会在objects这个目录中做一个快照,说白了就是复制一份当前版本的文件,然后通过索引控制这个文件是属于哪个版本的.
也就是这个原因,对一个大文件多次修改提交版本,会造成\.git目录越来越庞大.
不过git在保存文件的时候也做了一些压缩的操作.
查看jgit中这个类:org.eclipse.jgit.storage.file.ObjectDirectoryInserter
如下:
private File toTemp(final MessageDigest md, final int type, long len, final InputStream is) throws IOException, FileNotFoundException, Error { boolean delete = true; File tmp = newTempFile(); try { FileOutputStream fOut = new FileOutputStream(tmp); try { OutputStream out = fOut; if (config.getFSyncObjectFiles()) out = Channels.newOutputStream(fOut.getChannel()); DeflaterOutputStream cOut = compress(out); DigestOutputStream dOut = new DigestOutputStream(cOut, md); writeHeader(dOut, type, len); final byte[] buf = buffer(); while (len > 0) { int n = is.read(buf, 0, (int) Math.min(len, buf.length)); if (n <= 0) throw shortInput(len); dOut.write(buf, 0, n); len -= n; } dOut.flush(); cOut.finish(); } finally { if (config.getFSyncObjectFiles()) fOut.getChannel().force(true); fOut.close(); } delete = false; return tmp; } finally { if (delete) FileUtils.delete(tmp); } }
Jgit中有一个叫org.eclipse.jgit.lib.Repository的类,它是贯穿整个Git系统的线索,其用法:
FileRepositoryBuilder builder = new FileRepositoryBuilder(); Repository repository = builder .setGitDir(new File("D:\\testGit\\.git")).readEnvironment() // scan // environment // GIT_* // variables .findGitDir() // scan up the file system tree .build(); // 创建默认目录结构,如果没有.git目录的时候创建,如果有的时候创建的话会出现异常 // repository.create();
在objects目录中,有很多名字类似:fd36ba1c6ca79657d06bdc57511161fc03fae9这样的文件,其实这些文件的命名是通过sha-1算法算出来的,具体在
ObjectId org.eclipse.jgit.lib.ObjectInserter.idFor(int type, byte[] data, int off, int len)中实现这样的算法
/** * Compute the name of an object, without inserting it. * * @param type * 根据Type来造出不同类型头,有OBJ_COMMIT、OBJ_TREE、OBJ_BLOB、OBJ_TAG * @param data * 文件的内容 * @param off * first position within {@code data}. * @param len * number of bytes to copy from {@code data}. * @return the name of the object. */ public ObjectId idFor(int type, byte[] data, int off, int len) { MessageDigest md = digest(); md.update(Constants.encodedTypeString(type)); md.update((byte) ' '); md.update(Constants.encodeASCII(len)); md.update((byte) 0); md.update(data, off, len); return ObjectId.fromRaw(md.digest()); }
上面的类有两个重要的参数type和data,data就是文件的内容,type包含OBJ_COMMIT、OBJ_TREE、OBJ_BLOB、OBJ_TAG这四种类型,tree代表了提交的目录,tag是指出当前Object的一些信息。blod就是文件,其实就是真正被版本跟踪的文件。commit是指代一次commit操作,有了这几个参数,git把commit、tree、tag、blod这些object(在objects目录里面,以上四个操作都存储为object)区分开。
也就是说被版本跟踪的文件,只要发生那怕一个字节的变化,都会产生不同的sha-1码,这个时候,git就会到他的objects目录里面查找有没有这个文件(根据sha-1),没有则创建。具体代码看
ObjectDirectory的insert方法
public ObjectId insert(int type, byte[] data, int off, int len) throws IOException { ObjectId id = idFor(type, data, off, len); if (db.has(id)) { return id; } else { File tmp = toTemp(type, data, off, len); return insertOneObject(tmp, id); } }
最后把这个临时文件压入db中,也就是文件系统,objects目录下。
另外,/.git目录下面还有个index文件,这个文件记录是索引objects中已经存在的文件,把我们真正被跟踪的文件与存放在objects目录中的文件链接起来(被跟踪的文件路径-objects中的sha-1文件名)
/.git目录下有个 HEAD文件,内容是告诉git当前指向哪个分支,\.git\refs\heads\ 目录里面的文件就是各个分支,以及他的sha-1码,这个sha-1会与objects中一个文件相对应.在每产生一个版本,例如:commit这些操作都会引起\.git\refs\heads\ HEAD所指的分支的内容改变.也会产生一个新的sha-1名字的文件在objects中,这将会在我第二篇关于git文章进行详细的分析.
checkout和switch bracnch的操作都会引起HEAD的内容改变.
\.git\logs\refs\heads\中的内容是记录版本的记录,以方便回滚版本,这里的记录都是每\.git\refs\heads\个分支文件的sha-1改变记录.