I'm trying to get the first commit of a given git item using the get-commits API endpoint: https://docs.microsoft.com/en-us/rest/api/azure/devops/git/commits/get-commits?view=azure-devops-rest-6.0.
My issue is that if a file has been renamed, then the commit history of the file before the rename is lost. Using git from a command line I can add the --follow flag to return the history of a file before the rename.
I can't find an equivalent operation using the Azure DevOps Git REST API. Does one exist?
Some additional information: I'm trying to get the first commit for a given item in order to get the first author of the item. It seems there's no direct way to get the author without querying for the commits first, which is why I'm trying to get the first commit.
EDIT: Given TTT's answer, I've added the output to show the difference between the full-history simplify-merges flags and the follow flag:
--full-history --simplify-merges
$ git log --full-history --simplify-merges -- Custom/Scripts/js_file_renamed.js
commit xxx
Author: xxx xxx <xxx.com>
Date: Mon Feb 14 12:25:49 2022 0100
Rename commit
vs
--follow
$ git log --follow -- Custom/Scripts/js_file_renamed.js
commit xxx
Author: xxx xxx <[email protected]>
Date: Mon Feb 14 12:25:49 2022 0100
Rename commit
commit xxx
Author: xxx xxx <[email protected]>
Date: Mon Feb 14 11:22:29 2022 0000
Revert "Content migrated."
commit xxx
Author: xxx xxx <[email protected]>
Date: Mon Feb 14 11:20:14 2022 0000
Content migrated.
commit xxx
Author: xxx xxx <[email protected]>
Date: Mon Feb 14 12:14:08 2022 0100
Add folders
EDIT 2:
Given that I haven't found a direct equivalent to 'git log --follow', I did the following as a workaround:
- Perform a GET commits with the path of the file and an item descriptor containing the branch that file is in.
- If the oldest obtained commit change to the file is an add, then return that commit. If it is a rename, then use GET commit using the commit's ID to get a more detailed model with parent commits.
- Take the first parent commit and compare diffs with the rename commit to obtain the previous name of the file.
- Recurse, passing in the previous name of the file, and the parent commit id as the item descriptor.
In pseudocode it would look like this:
private GitCommit GetFirstCommit(GitVersionDescriptor itemVersion, string path)
{
commitQuery = new GitQueryCommitsCriteria
{
ItemVersion = itemVersion,
ItemPath = path
}
commits = GET commits(commitQuery)
firstCommit = commits.Last()
if (!firstCommit) {
return null;
}
isAdd = firstCommit.Changes.Any(y => y.ChangeType.HasFlag(VersionControlChangeType.Add));
isRename = firstCommit.Changes.Any(y => y.ChangeType.HasFlag(VersionControlChangeType.Rename));
fullCommitDetails = GET commit(firstCommit.CommitId);
if (isAdd)
{
return fullCommitDetails
}
if (isRename)
{
parentId = fullCommitDetails.Parents.First();
if (!parentId)
{
return null;
}
baseVersionDescriptor = new GitBaseVersionDescriptor() { VersionType = GitVersionType.Commit, Version = parentId };
targetVersionDescriptor = new GitTargetVersionDescriptor() { VersionType = GitVersionType.Commit, Version = firstCommit.CommitId };
diffs = GET diffs(baseVersionDescriptor, targetVersionDescriptor)
previousPath = diffs.Changes
.Where(x =>
x.ChangeType.HasFlag(VersionControlChangeType.Rename) &&
x.Item.Path == path)
.Select(x => x.SourceServerItem)
.SingleOrDefault()
if (!previousPath)
{
return null;
}
return GetFirstCommit(baseVersionDescriptor, previousPath);
}
return null;
}
CodePudding user response:
Adding an answer to my own question since I haven't found a direct equivalent to 'git log --follow'. I did the following as a workaround:
- Perform a GET commits with the path of the file and an item descriptor containing the branch that file is in.
- If the oldest obtained commit change to the file is an add, then return that commit. If it is a rename, then use GET commit using the commit's ID to get a more detailed model with parent commits.
- Take the first parent commit and compare diffs with the rename commit to obtain the previous name of the file.
- Recurse, passing in the previous name of the file, and the parent commit id as the item descriptor.
In pseudocode it would look like this:
private GitCommit GetFirstCommit(GitVersionDescriptor itemVersion, string path)
{
commitQuery = new GitQueryCommitsCriteria
{
ItemVersion = itemVersion,
ItemPath = path
}
commits = GET commits(commitQuery)
firstCommit = commits.Last()
if (!firstCommit) {
return null;
}
isAdd = firstCommit.Changes.Any(y => y.ChangeType.HasFlag(VersionControlChangeType.Add));
isRename = firstCommit.Changes.Any(y => y.ChangeType.HasFlag(VersionControlChangeType.Rename));
fullCommitDetails = GET commit(firstCommit.CommitId);
if (isAdd)
{
return fullCommitDetails
}
if (isRename)
{
parentId = fullCommitDetails.Parents.First();
if (!parentId)
{
return null;
}
baseVersionDescriptor = new GitBaseVersionDescriptor() { VersionType = GitVersionType.Commit, Version = parentId };
targetVersionDescriptor = new GitTargetVersionDescriptor() { VersionType = GitVersionType.Commit, Version = firstCommit.CommitId };
diffs = GET diffs(baseVersionDescriptor, targetVersionDescriptor)
previousPath = diffs.Changes
.Where(x =>
x.ChangeType.HasFlag(VersionControlChangeType.Rename) &&
x.Item.Path == path)
.Select(x => x.SourceServerItem)
.SingleOrDefault()
if (!previousPath)
{
return null;
}
return GetFirstCommit(baseVersionDescriptor, previousPath);
}
return null;
}