1
votes

I am currently trying to update a submodule's commit id automatically when another project has certain files changed. I have a .net webhook and I'm using the octokit.net library.

I can see in the github documentation (https://developer.github.com/v3/git/trees/#create-a-tree) there is a submodule option when creating a new tree that allows you to add a commit and path, but I can't get it to work. Octokit also has a submodule type for the NewTreeItem/TreeItem objects but no examples or documentation.

My current code is here - currently I'm passing the commit sha in as the sha parameter, but I can see this is wrong, I need to create a commit on that repo and use that sha, I just don't know how to do that before the tree is created and there isn't any documentation that I can find:

    public static async Task UpdateSubmoduleInBranch(string repo, string branchName, string submodulePath, string sha, string commitComment, GitHubClient github = null)
    {
        //hoping this will update the sha of a submodule

        // url encode branch name for github operations
        branchName = HttpUtility.UrlEncode(branchName);

        if (github == null) github = GetClient();

        var repoId = (await github.Repository.Get(Settings.GitHub.OrgName, repo)).Id;

        RepositoriesClient rClient = new RepositoriesClient(new ApiConnection(github.Connection));

        var branch = await rClient.Branch.Get(repoId, branchName);

        var tree = await github.Git.Tree.Get(repoId, branchName);

        var newTree = new NewTree { BaseTree = tree.Sha };
        newTree.Tree.Add(new NewTreeItem
        {
            Mode = Octokit.FileMode.Submodule,
            Path = submodulePath,
            Type = TreeType.Commit,
            Sha = sha
        });

        var createdTree = await github.Git.Tree.Create(repoId, newTree);

        var newCommit = new NewCommit(commitComment, createdTree.Sha, new[] { branch.Commit.Sha });
        newCommit.Committer = Settings.GitHub.Committer;

        var createdCommit = await github.Git.Commit.Create(Settings.GitHub.OrgName, Settings.GitHub.AppName, newCommit);

        var updateRef = new ReferenceUpdate(createdCommit.Sha, false);
        await github.Git.Reference.Update(repoId, "heads/" + branchName, updateRef);
    }

Edit

In case anyone else is looking for this, I resolved the issue - the octokit api does not support this action, even if it looks like it does.

In addition to the PatchAsync method found at PATCH Async requests with Windows.Web.Http.HttpClient class, the following code worked for me:

    public static async Task UpdateSubmoduleInBranch(string repo, string branchName, string submodulePath, string sha, string commitComment)
    {
        using (var client = new HttpClient())
        {
            try
            {
                //these headers authenticate with github, useragent is required.
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Settings.GitHub.AuthToken);
                client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("mathspathway-environment-manager", "v1.0"));

                var committer = Settings.GitHub.Committer;

                //get the branch, and collect the sha of the current commit
                var branchResponse = await client.GetAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/branches/{branchName}");
                JToken branchResult = JToken.Parse(await branchResponse.Content.ReadAsStringAsync());
                var currentCommitSha = branchResult["commit"].Value<string>("sha");

                //create the new tree, with the mode of 160000 (submodule mode) and type of commit, and the sha of the other 
                //repository's commit that you want to update the submodule to, and the base tree of the current commit on this repo
                var newTreeObj = new
                {
                    base_tree = currentCommitSha,
                    tree = new List<Object> { new { path = submodulePath, mode= "160000", type = "commit", sha = sha}
            }
                };

                HttpContent treeHttpContent = new StringContent(JsonConvert.SerializeObject(newTreeObj));
                var treeResponse = await client.PostAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/git/trees", treeHttpContent);
                var treeResponseContent = JToken.Parse(await treeResponse.Content.ReadAsStringAsync());
                var treeSha = treeResponseContent.Value<string>("sha");


                //Create a new commit based on the tree we just created, with the parent of the current commit on the branch
                var newCommitObj = new
                {
                    message = commitComment,
                    author = new { name = committer.Name, email = committer.Email, date = committer.Date },
                    parents = new[] { currentCommitSha },
                    tree = treeSha
                };
                HttpContent newCommitContent = new StringContent(JsonConvert.SerializeObject(newCommitObj));
                var commitResponse = await client.PostAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/git/commits", newCommitContent);
                var commitResponseContent = JToken.Parse(await commitResponse.Content.ReadAsStringAsync());
                var commitSha = commitResponseContent.Value<string>("sha");



                //create an update reference object, and update the branch's head commit reference to the new commit
                var updateRefObject = new { sha = commitSha, force = false };
                HttpContent updateRefContent = new StringContent(JsonConvert.SerializeObject(updateRefObject));
                var updateRefResponse = await client.PatchAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/git/refs/heads/{branchName}", updateRefContent);

            } catch (Exception ex)
            {
                Debug.WriteLine($"Error occurred updating submodule: {ex.Message}{Environment.NewLine}{Environment.NewLine}{ex.StackTrace}");
            }
        }
    }
1
I don't suppose you remember what error it was giving you? I was able to get this to work almost exactly as you describe your original code. The only difference I had was, in the new NewCommit line, I used the overload that takes a single SHA as the last param instead of the enumerable one. Gistpatridge
I don't remember the exact error - interesting, I might have been a lot closer than I thought. I got a reply from github support that said it wasn't supported and I had to use the git data api. From the email: > Repo Contents API doesn't support updating submodules: > developer.github.com/v3/repos/contents > However, you should be able to use the low-level Git Data API to update a submodule.Dylan Hall
It could have even been a bug in the framework at the time that is now fixed. Either way, at least you found something that worked at the time.patridge

1 Answers

0
votes

While trying to accomplish roughly the same goal, but via a separate user's submitting a pull request, I gave your original code a shot with some minor changes. The result was that it worked perfectly with Octokit.net.

Extracted sample code

var submoduleRepoId = (await gitHubClient.Repository.Get(submoduleRepoOwnerName, submoduleRepoName)).Id;
var submoduleRepoBranchLatestSha = (await gitHubClient.Git.Tree.Get(submoduleRepoId, submoduleRepoBranchName)).Sha;
…
var updateParentTree = new NewTree { BaseTree = parentRepoBranchLatestSha };
updateParentTree.Tree.Add(new NewTreeItem
{
    Mode = FileMode.Submodule,
    Sha = submoduleRepoBranchLatestSha,
    Path = pathToSubmoduleInParentRepo,
    Type = TreeType.Commit,
});
var newParentTree = await gitHubClient.Git.Tree.Create(pullRequestOwnerForkRepoId, updateParentTree);
var commitMessage = $"Bump to {submoduleOwnerName}/{submoduleRepoName}@{submoduleCommitHash}";
var newCommit = new NewCommit(commitMessage, newParentTree.Sha, parentBranchLatestSha);
var pullRequestBranchRef = $"heads/{pullRequestBranchName}";
var commit = await gitHubClient.Git.Commit.Create(pullRequestOwnerName, parentRepoName, newCommit);
var await gitHubClient.Git.Reference.Update(pullRequestOwnerForkRepoId, pullRequestBranchRef, new ReferenceUpdate(commit.Sha));

Full sample code

At this point, there are only a few potential differences I can see.

  • I definitely do not HttpUtility.UrlEncode my branch name (Octokit must be doing any needed encoding for me)
  • I am committing to a separate user's branch on their own fork

It could be those differences were sufficient or maybe a bug has been worked out that was around when you were attempting the same thing.