JGitで git log -p を再現する

JGitを研究で使わなきゃならなくなった。
これでgit log -pを再現するのがえらい時間がかかったのでメモ。
一部EGitのソースを流用してる。

以下のcheckGitInfo()を実行するとこんな感じで取得が可能である。

.gitignore MODIFY
 old 
*.csv
*.xml

 new 
*.tsv

ソースコード中のprojectPathはGitリポジトリがあるファイルのString型パス。

流れとしては、

  1. Gitのリポジトリをプロジェクトパスを指定して取得。
  2. LogCommandによってlogのイテレータを取得(返り値はIterable)
  3. 取得したイテレータから前のやつと後のやつのペアを取るようにループを回す。(この時参照渡しに注意。)
  4. リポジトリをセットしたDiffFormatterのscanメソッドを用いて前のヤツと後のヤツの差分情報(DiffEntryオブジェクトで保存)のリストを取る
  5. DiffEntryオブジェクトから、差分の行数を保存しているEditオブジェクトのリストを取得する
  6. 同じくDiffEntryオブジェクトから新旧のテキストをRawTextオブジェクトとして取得。

と言った感じ。

	// blobIdが持つファイルの情報を持つRawTextインスタンスを返すメソッド(Egitのソースより引用)
	private static RawText readText(AbbreviatedObjectId blobId,
			ObjectReader reader) throws IOException {
		ObjectLoader oldLoader = reader.open(blobId.toObjectId(),
				Constants.OBJ_BLOB);
		return new RawText(oldLoader.getCachedBytes());
	}

	public void checkGitInfo() {
		try {
			Repository repo = new RepositoryBuilder()
					.findGitDir(new File(projectPath)).readEnvironment()
					.findGitDir().build();
			Git git = new Git(repo);
			DiffAlgorithm diffAlgorithm = DiffAlgorithm.getAlgorithm(repo
					.getConfig().getEnum(ConfigConstants.CONFIG_DIFF_SECTION,
							null, ConfigConstants.CONFIG_KEY_ALGORITHM,
							SupportedAlgorithm.HISTOGRAM));
			ObjectReader reader = repo.newObjectReader();

			Iterable<RevCommit> log = git.log().all().call();
			DiffFormatter diffFormatter = new DiffFormatter(System.out);
			diffFormatter.setRepository(repo);
			AnyObjectId prevTree = null;
			AnyObjectId currentTree = null;
			if (log.iterator().hasNext()) {
				currentTree = log.iterator().next().getTree();
			}
			while (log.iterator().hasNext()) {
				prevTree = currentTree.copy();
				currentTree = log.iterator().next().getTree();
				System.out.println(currentTree);
				List<DiffEntry> list = diffFormatter
						.scan(prevTree, currentTree);

				for (DiffEntry diffEntry : list) {
					// DELETEの差分はnewPathが"dev/null"になってぬるぽで怒られるため、とりあえず退避
					if (diffEntry.getChangeType() != DiffEntry.ChangeType.DELETE) {
						RawText oldText = readText(diffEntry.getOldId(), reader);
						RawText newText = readText(diffEntry.getNewId(), reader);
						EditList editList = diffAlgorithm.diff(
								RawTextComparator.DEFAULT, oldText, newText);
						for (Edit edit : editList) {
							System.out.println(diffEntry.getNewPath()
									+" " + diffEntry.getChangeType()
									+ "\n old \n"
									+ oldText.getString(edit.getBeginA(),
											edit.getEndA(), false)
									+ "\n new \n"
									+ newText.getString(edit.getBeginB(),
											edit.getEndB(), false));
						}

					}
				}
				System.out.println();
			}

		} catch (IOException e) {
			e.printStackTrace();
		} catch (NoHeadException e) {
			e.printStackTrace();
		} catch (GitAPIException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}

1. Gitのリポジトリをプロジェクトパスを指定して取得。

この辺。まあこれは、JGitのUser Guideにも書いてるので迷わない。

			Repository repo = new RepositoryBuilder()
					.findGitDir(new File(projectPath)).readEnvironment()
					.findGitDir().build();
			Git git = new Git(repo);

2. LogCommandによってlogのイテレータを取得(返り値はIterable)

Iterable<RevCommit> log = git.log().all().call();

call()を忘れない。

3. 取得したイテレータから前のやつと後のやつのペアを取るようにループを回す。

			AnyObjectId prevTree = null;
			AnyObjectId currentTree = null;
			if (log.iterator().hasNext()) {
				currentTree = log.iterator().next().getTree();
			}
			while (log.iterator().hasNext()) {
				prevTree = currentTree.copy();
				currentTree = log.iterator().next().getTree();
				// 以下中略
			}

イテレータが 2. で取れたので、それをwhile文で回す。
RevCommitオブジェクトを取ればいいのだが、手っ取り早くインスタンスのクローンを作るメソッドがAnyObjectId型でしか返さないのでめんどくさくなってこれを取得するようにした。
クローンを作っておかないと、こんな感じでループを回すとcurrentTreeのポインタが上書きされてprevTreeとcurrentTreeが同じで差分もクソもなくなるので注意。

細かいデータが欲しい場合RevCommitクラスのフィールドが欲しくなるので保存しておくと吉。

4. リポジトリをセットしたDiffFormatterのscanメソッドを用いて前のヤツと後のヤツの差分情報(DiffEntryオブジェクトで保存)のリストを取る

List<DiffEntry> list = diffFormatter.scan(prevTree, currentTree);

diffFormatterというのは、diffを取るために必要なフォーマットを指定するクラスらしい…
よくわからないので、とりあえず標準出力を指定しておいた。
これは、1つのGitオブジェクトに対して1個あればOK。

5. DiffEntryオブジェクトから、差分の行数を保存しているEditオブジェクトのリストを取得する

RawText oldText = readText(diffEntry.getOldId(), reader);
RawText newText = readText(diffEntry.getNewId(), reader);

readTextメソッドの詳細は分からないが、とりあえずこれで取れている…

6. 同じくDiffEntryオブジェクトから新旧のテキストをRawTextオブジェクトとして取得。

EditList editList = diffAlgorithm.diff(RawTextComparator.DEFAULT, oldText, newText);

厄介なのがこの辺り。
何故か、diffEntryからは行数の情報は取れないので、diffAlgorithmオブジェクトを作ってあげて、そこからdiff情報をEditオブジェクトのリスト(EditListは実質ArrayList)として受け取る。

また、このEditオブジェクトには古い方をA、新しい方をBとして
それぞれの差分が生まれた行数をstartA. endA, startB, endB みたいな感じでフィールドに保存しているので、それを使ってRawTextから差分情報を引っ張ってくる。

RawText.getString()は引数にどこからどこまで、というのを指定できるのでこいつと上のフィールドのゲッタを使ってサクッと差分だけのStringを取ってこれる。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください