Ad

Is There An Equivalent Azure DevOps Git REST API Operation To 'git Log --follow -- '?

- 1 answer

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:

  1. Perform a GET commits with the path of the file and an item descriptor containing the branch that file is in.
  2. 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.
  3. Take the first parent commit and compare diffs with the rename commit to obtain the previous name of the file.
  4. 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;
}
Ad

Answer

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:

  1. Perform a GET commits with the path of the file and an item descriptor containing the branch that file is in.
  2. 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.
  3. Take the first parent commit and compare diffs with the rename commit to obtain the previous name of the file.
  4. 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;
}
Ad
source: stackoverflow.com
Ad