Robin
Robin

Reputation: 528

Azure Devops API: Find a Git commit by Sha1 prefix, branch name or tag name?

In a local Git installation, I can use "git show" to find a specific commit, given a SHA1 hash prefix, a branch name, or a tag name.

How can I achieve the same thing with the TFS/Azure Devops API?

I have tried Microsoft.TeamFoundation.SourceControl.WebApi.GitHttpClientBase.[GetCommitsAsync][1](), but for the SearchCriteria.ItemVersion and CompareVersion, I need to provide a VersionType (Branch, Tag or Commit), so unlike git-show I already have to know what kind of identifier I have. Besides, in case of "Commit", it has to be a complete SHA1 hash; a prefix won't do. Besides, I want to find exactly one commit, not a whole list.

And Microsoft.TeamFoundation.SourceControl.WebApi.GitHttpClientBase.GetCommitAsync() only works with a complete SHA1 hash, not with a prefix, or a branch or tag.

Is there any API call equivalent to "git show"?

In case the version is important, it is a TFS server 2019 (Dev17.M153.5).

Upvotes: 0

Views: 2939

Answers (2)

Robin
Robin

Reputation: 528

Using Jesse's hints, I've come up with this implementation:

    public async Task<GitCommit> GetCommitClever(string id)
    {
        return await GetCommitFromTag(id)
            ?? await GetCommitFromBranch(id)
            ?? await GetCommitFromHashPrefix(id);
    }

    private async Task<GitCommit> GetCommit(string commitId)
    {
        return await m_gitClient.GetCommitAsync(commitId, m_repo.Id).ConfigureAwait(false);
    }

    private async Task<GitCommit> GetCommitFromTag(string id)
    {
        var refs = await m_gitClient.GetRefsAsync(m_repo.Id, filter: "tags/" + id).ConfigureAwait(false);
        if (refs == null || refs.Count != 1)
        {
            return null;
        }

        var tag = await m_gitClient.GetAnnotatedTagAsync(m_repo.ProjectReference.Id, m_repo.Id, refs[0].ObjectId).ConfigureAwait(false);
        return await GetCommit(tag.TaggedObject.ObjectId);
    }

    private async Task<GitCommit> GetCommitFromBranch(string id)
    {
        var refs = await m_gitClient.GetRefsAsync(m_repo.Id, filter: "heads/" + id).ConfigureAwait(false);
        if (refs == null || refs.Count != 1)
        {
            return null;
        }

        return await GetCommit(refs.Single().ObjectId).ConfigureAwait(false);
    }

    private async Task<GitCommit> GetCommitFromHashPrefix(string id)
    {
        if (!System.Text.RegularExpressions.Regex.IsMatch(id, @"\A\b[0-9a-fA-F]+\b\Z"))
        {
            throw new ArgumentException($"Branch or tag '{id}' not found.");
        }

        GitQueryCommitsCriteria crit = new GitQueryCommitsCriteria()
        {
            FromCommitId = id.PadRight(40, '0'),
            ToCommitId = id.PadRight(40, 'f')
        };
        var refs = await m_gitClient.GetCommitsAsync(m_repo.Id, crit, top: 2).ConfigureAwait(false);
        if (refs == null || refs.Count == 0)
        {
            throw new ArgumentException($"Commit '{id}' not found.");
        }
        if (refs.Count > 1)
        {
            throw new ArgumentException($"Commit id '{id}' not unique.");
        }

        return await GetCommit(refs.Single().CommitId).ConfigureAwait(false);
    }

    // Using these private members:
    private GitHttpClient m_gitClient;
    private GitRepository m_repo;

There's room for improvement; e.g. if you have a tag "foobar" and a branch "foo", then getting a commit by id "foo" will return the tag instead of the better-matching branch. But I only need limited cleverness, so this will do.

Upvotes: 0

jessehouwing
jessehouwing

Reputation: 114491

Azure DevOps has a Search box on the commits page in the UI. Using that with a partial hash gives the following query criteria. It uses the GET Commit REST method:

  {
    "fromCommitId": "b86c400000000000000000000000000000000000",
    "toCommitId": "b86c4fffffffffffffffffffffffffffffffffff",
    "skip": 0,
    "maxResultCount": 25
  }

Or set the searchCriteria.itemVersion.version to the exact name and searchCriteria.itemVersion.versionType to branch or tag or commit to search for a specific commit in case you know the full name (branch, tag or full commit id).

I used the Chrome Dev Tools to capture the request. Worst case the short hash isn't unique and it will return multiple options.

For branches and tags you can also use the refs API, which has a start-with and a contains option to find the ref you're looking for.

Example:

GET https://dev.azure.com/fabrikam/_apis/git/repositories/{repositoryId}/refs?filter=heads/&filterContains=replacer&api-version=6.0

Search for filter=heads/ for branches and filter=tags/ for tags, or leave the main filter empty to search both.

In theory this API can return multiple objects, since it uses startsWith for the filter and contains for the filterContains.

Upvotes: 1

Related Questions