Цикл по коммитам для файла с jGit
Мне удалось разобраться с основами jGit-файла с точки зрения подключения к репозиториям и добавления, фиксации и даже зацикливания сообщений фиксации для файлов.
File gitDir = new File("/Users/myname/Sites/helloworld/.git");
RepositoryBuilder builder = new RepositoryBuilder();
Repository repository;
repository = builder.setGitDir(gitDir).readEnvironment()
.findGitDir().build();
Git git = new Git(repository);
RevWalk walk = new RevWalk(repository);
RevCommit commit = null;
// Add all files
// AddCommand add = git.add();
// add.addFilepattern(".").call();
// Commit them
// CommitCommand commit = git.commit();
// commit.setMessage("Commiting from java").call();
Iterable<RevCommit> logs = git.log().call();
Iterator<RevCommit> i = logs.iterator();
while (i.hasNext()) {
commit = walk.parseCommit( i.next() );
System.out.println( commit.getFullMessage() );
}
Далее я хочу получить все сообщения о фиксации для одного файла, а затем вернуть один файл обратно к определенной ссылке / моменту времени.
4 ответа
Вот как найти изменения коммита на основе всех родительских коммитов
var tree = new TreeWalk(repository)
tree.addTree(commit.getTree)
commit.getParents foreach {
parent => tree.addTree(parent.getTree)
}
tree.setFilter(TreeFilter.ANY_DIFF)
(Скала код)
Обратите внимание, что TreeFilter.ANY_DIFF работает для одного обходчика дерева и возвращает все элементы, доступные в корневом коммите.
Затем вам придется перебирать дерево, чтобы увидеть, находится ли ваш файл в заданной дельте (это довольно просто).
while (tree.next)
if (tree.getDepth == cleanPath.size) {
// we are at the right level, do what you want
} else {
if (tree.isSubtree &&
name == cleanPath(tree.getDepth)) {
tree.enterSubtree
}
}
}
(cleanPath - это путь чистого репо, разделенный на '/')
Теперь оберните этот код в цикл RevWalk.next, и вы получите коммиты и файлы, измененные коммитом.
Возможно, вы захотите использовать фильтр, отличный от ANY_DIFF, потому что ANY_DIFF имеет значение true, если одно дерево отличается. Это немного нелогично в случае слияния, когда BLOB-объект не изменился по сравнению со всеми родительскими деревьями. Итак, вот ядро ALL_DIFF, которое будет показывать только элементы, которые отличаются от всех родительских деревьев:
override def include(walker: TreeWalk): Boolean = {
val n = walker.getTreeCount();
if (n == 1) {
return true;
}
val m = walker.getRawMode(0)
var i = 1
while (i < n) {
if (walker.getRawMode(i) == m && walker.idEqual(i, 0)) {
return false
}
i += 1
}
true
}
(Scala-код, полученный из AnyDiffFilter)
Поэтому я попытался заставить работать решение Чарлибоя, но в основном это получалось, но в следующем случае у меня ничего не вышло (возможно, что-то изменилось в jgit с тех пор?)
добавить файл A, зафиксировать как "commit 1" добавить файл B, зафиксировать как "commit 2"
getFileVersionDateList("fileA")
И то и другое commit 1
а также commit 2
были найдены там, где я ожидал только commit 1
,
Мое решение было следующим:
List<Commit> commits = new ArrayList<Commit>();
RevWalk revWalk = new RevWalk(repository);
revWalk.setTreeFilter(
AndTreeFilter.create(
PathFilterGroup.createFromStrings(<relative path in question>),
TreeFilter.ANY_DIFF)
);
RevCommit rootCommit = revWalk.parseCommit(repository.resolve(Constants.HEAD));
revWalk.sort(RevSort.COMMIT_TIME_DESC);
revWalk.markStart(rootCommit);
for (RevCommit revCommit : revWalk) {
commits.add(new GitCommit(getRepository(), revCommit));
}
Использование LogCommand еще проще и выглядит так:
List<Commit> commitsList = new ArrayList<Commit>();
Git git = new Git(repository);
LogCommand logCommand = git.log()
.add(git.getRepository().resolve(Constants.HEAD))
.addPath(<relative path in question>);
for (RevCommit revCommit : logCommand.call()) {
commitsList.add(new GitCommit(this, revCommit));
}
Очевидно, что при необходимости вы также проверяете даты фиксации и т. Д.
Общий подход с помощью git для поиска истории конкретного файла состоит в том, чтобы пройтись по графу ревизий (который вы делаете) и для каждого из них протестировать объект, на который указывает рассматриваемый путь (может быть как BLOB-объектом, так и деревом). найти историю для всего поддерева). Так что, по сути, действуйте как фильтр на выходе ревизионного набора от ревизора ревизий.
Документация jgit кажется... разреженной. Но вы должны иметь возможность получить RevTree, соответствующий каждому RevCommit, и, если необходимо, пройти через это с каждым сегментом пути по очереди до идентификатора объекта терминала.
araqnid прав, вот как я получил список дат, каждая дата, связанная с коммитом, включала рассматриваемый файл...
Затем вы можете извлечь файл из определенного коммита, так как у вас есть имя файла и дата коммита, см. Два метода ниже....
примечание: этот код относится к классу.groovy, поэтому вам, без сомнения, придется немного изменить Java.
byte[] getAnyPreviousVersionFileBytes(String relativeFilePath, Date date) {
byte[] bytes = null
try {
RevWalk revWalk = new RevWalk(repository)
ObjectId headId = repository.resolve(Constants.HEAD);
RevCommit root = revWalk.parseCommit(headId);
revWalk.sort(RevSort.COMMIT_TIME_DESC);
revWalk.markStart(root);
for (RevCommit revCommit: revWalk) {
// if date matches then walk the tree in this commit
if (new Date(revCommit.commitTime * 1000L) == date) {
TreeWalk treeWalk = TreeWalk.forPath(repository, relativeFilePath, revCommit.getTree())
if (treeWalk != null) {
treeWalk.setRecursive(true)
CanonicalTreeParser canonicalTreeParser = treeWalk.getTree(0, CanonicalTreeParser)
while (!canonicalTreeParser.eof()) {
// if the filename matches, we have a match, so set teh byte array to return
if (canonicalTreeParser.getEntryPathString() == relativeFilePath) {
ObjectLoader objectLoader = repository.open(canonicalTreeParser.getEntryObjectId())
bytes = objectLoader.bytes
}
canonicalTreeParser.next(1)
}
}
}
}
}
catch (Exception e) {
throw new JgitException(e)
}
return bytes
}
List<Date> getFileVersionDateList(String relativeFilePath) {
List<Date> versions = new LinkedList<Date>()
try {
RevWalk revWalk = new RevWalk(repository)
ObjectId headId = repository.resolve(Constants.HEAD);
RevCommit root = revWalk.parseCommit(headId);
revWalk.sort(RevSort.COMMIT_TIME_DESC);
revWalk.markStart(root);
for (RevCommit revCommit: revWalk) {
TreeWalk treeWalk = TreeWalk.forPath(repository, relativeFilePath, revCommit.getTree())
if (treeWalk != null) {
treeWalk.setRecursive(true)
CanonicalTreeParser canonicalTreeParser = treeWalk.getTree(0, CanonicalTreeParser)
while (!canonicalTreeParser.eof()) {
// if the filename matches, we have a match, so add the date of this commit to the list
if (canonicalTreeParser.getEntryPathString() == relativeFilePath) {
versions.add(new Date(revCommit.commitTime * 1000L))
}
canonicalTreeParser.next(1)
}
}
}
}
catch (Exception e) {
throw new JgitException(e)
}
return versions
}