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
如下:

	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改变记录.