Updating a file in GitHub repository using Octokit - c#

I am trying to develop a windows forms application that can create, update and delete files in a GitHub repository using Octokit.
public Form1()
{
InitializeComponent();
var ghClient = new GitHubClient(new ProductHeaderValue("Octokit-Test"));
ghClient.Credentials = new Credentials("-personal access token here-");
// github variables
var owner = "username";
var repo = "repository name";
var branch = "master";
// create file
//var createChangeSet = ghClient.Repository.Content.CreateFile(owner,repo,"path/file2.txt",new CreateFileRequest("File creation", "Hello World!", branch));
// update file
var updateChangeSet = ghClient.Repository.Content.UpdateFile(owner, repo,"path/file2.txt", new UpdateFileRequest("File update","Hello Universe!", "SHA value should be here", branch));
}
Firstly, I managed to create a file (check the commented out code), which is fully functional. Then I tried to update that file using,
var updateChangeSet = ghClient.Repository.Content.UpdateFile(owner, repo,"path/file2.txt", new UpdateFileRequest("File update","Hello Universe!", "SHA value should be here", branch));
As you can see, in this situation, I have to get the sha value since the requirement for the "UpdateFileRequest" is,
UpdateFileRequest(string message, string content, string sha, string branch)
How can I receive this Sha value for my file from GitHub?
I am following this tutorial but when I try "createChangeSet.Content.Sha"(without commenting out createChangeSet), it draws a red line underneath "Content" and says,
Task<RepositoryChangeSet> does not contain a definition for 'Content' and no extention method 'Content' accepting a first argument of type Task<RepositoryChangeSet> could be found
I looked at GitHub Documentation and it says I should use,
GET /repos/:owner/:repo/contents/:path
to return the contents of a file or directory in a repository so I assume I will be able to obtain the sha value this way.
How can I implement this method to receive the sha value for my file in the repository so I can use that value to update the file?

I had the same problem and to get the sha you will need to get the existing file first and with this file you also get the last commit sha, which can be used to update the file.
Full demo code:
var ghClient = new GitHubClient(new ProductHeaderValue("Octokit-Test"));
ghClient.Credentials = new Credentials("//...//");
// github variables
var owner = "owner";
var repo = "repo";
var branch = "branch";
var targetFile = "_data/test.txt";
try
{
// try to get the file (and with the file the last commit sha)
var existingFile = await ghClient.Repository.Content.GetAllContentsByRef(owner, repo, targetFile, branch);
// update the file
var updateChangeSet = await ghClient.Repository.Content.UpdateFile(owner, repo, targetFile,
new UpdateFileRequest("API File update", "Hello Universe! " + DateTime.UtcNow, existingFile.First().Sha, branch));
}
catch (Octokit.NotFoundException)
{
// if file is not found, create it
var createChangeSet = await ghClient.Repository.Content.CreateFile(owner,repo, targetFile, new CreateFileRequest("API File creation", "Hello Universe! " + DateTime.UtcNow, branch));
}
I'm not sure if there is a better way to do it - if the searched file is not found an exception is thrown.
But it seems to work that way.

Related

Azure Batch - ResourceFiles PreparationTask

I'm trying to use a PreparationTask to grab my ResourceFiles to be used as input data.
My prep task looks like this:
myJob.JobPreparationTask = new JobPreparationTask { CommandLine = jobPrepCmdLine };
How do I configure my job with a PreparationTask to download ResourceFiles from my AutoStorageContainer to pool VM's?
I tried:
var inputFiles = new List<ResourceFile> { };
var file = ResourceFile.FromAutoStorageContainer("fgrp-jill2");
inputFiles.Add(file);
myJob.JobPreparationTask.ResourceFiles = inputFiles;
But get a null object error, even when the inputFiles.Add is showing at least 1 file recognized.
You should use the Storage SDK in conjunction with Batch in this scenario. You can follow this as an example: https://learn.microsoft.com/en-us/azure/batch/quick-run-dotnet#preliminaries
A Job preparation task functions very similar to a regular Start Task in that it runs before the tasks execution. In the example from the link, you'll see that we reference the Blob client, container name and file path. I'll paste the sample here:
List<string> inputFilePaths = new List<string>
{
"taskdata0.txt",
"taskdata1.txt",
"taskdata2.txt"
};
List<ResourceFile> inputFiles = new List<ResourceFile>();
foreach (string filePath in inputFilePaths)
{
inputFiles.Add(UploadFileToContainer(blobClient, inputContainerName, filePath));
}

UWP: GetFileAsync on a partial path?

I'm trying to get a single file in an UWP based on its partial name. Basically, I want to select a file that starts with "latest_" in a given folder.
Right now I can do that with that command:
var previousInfo = (await rootFolder.GetFilesAsync()).Where(file => file.DisplayName.StartsWith("latest_")).FirstOrDefault();
I suppose this code is not very efficient if there are a lot of files in the folder...
Is there a simpler way to do this? Like:
var previousInfo2 = await rootFolder.GetFileAsync("latest_*");
Or:
var previousInfo2 = await rootFolder.GetFileAsync(#"latest_*");
Right now this code is throwing an exception (invalid parameter). And the MSDN documentation about this function is very light: it just says that the function takes a string as parameter...
Thanks!
You can do this by using Advanced Query Syntax and in fact, you can filter/search by many more file metadata. Below is an example for search based on File Name.
var queryOptions = new QueryOptions();
queryOptions.ApplicationSearchFilter = "System.FileName:latest_*";
StorageFileQueryResult queryResult = rootFolder.CreateFileQueryWithOptions(queryOptions);
var files = await queryResult.GetFilesAsync();

Copying annotations leads to a corrupted attachment

I'm implementing a plugin (POST Quote Create, Synchronous, Sandbox) to make it so that Notes are copied to the new record when a quote is revised.
My plugin boils down to this (snippet):
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var Service = serviceFactory.CreateOrganizationService(context.UserId);
var notesQuery = new QueryExpression("annotation");
notesQuery.ColumnSet = new ColumnSet(true);
notesQuery.Criteria = new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("objecttypecode", ConditionOperator.Equal, "quote"),
new ConditionExpression("objectid", ConditionOperator.Equal, revisedQuoteId)
}
};
var notes = Service.RetrieveMultiple(notesQuery).Entities;
foreach (var n in notes)
{
var newNote = new Entity("annotation");
newNote.Attributes.Add("ownerid", n.GetAttributeValue<EntityReference>("ownerid"));
newNote.Attributes.Add("objectid", new EntityReference("quote", sourceEntity.Id));
newNote.Attributes.Add("objecttypecode", "quote");
newNote.Attributes.Add("subject", n.GetAttributeValue<string>("subject"));
newNote.Attributes.Add("notetext", n.GetAttributeValue<string>("notetext"));
newNote.Attributes.Add("isdocument", n.GetAttributeValue<bool>("isdocument"));
if (n.GetAttributeValue<bool>("isdocument"))
{
newNote.Attributes.Add("filesize", n.GetAttributeValue<int>("filesize"));
newNote.Attributes.Add("documentbody", n.GetAttributeValue<string>("documentbody"));
newNote.Attributes.Add("filename", n.GetAttributeValue<string>("filename"));
newNote.Attributes.Add("mimetype", n.GetAttributeValue<string>("mimetype"));
}
Service.Create(newNote);
}
Basically, I copy everything over, including an eventual attachment. Everything seems fine, the new revision shows fields, detail records and notes properly... everything but the attachment of the notes.
If I have a single note, with an attached test.txt which content is:
Test attachment
The OrganizationData service reads as follows:
<d:FileName>test.txt</d:FileName>
<d:FileSize m:type="Edm.Int32">39</d:FileSize>
<d:DocumentBody>H4sIAAAMaVMA/wtJLS5RSCwpSUzOyE3NK+HlAgCLmj1zEQAAAA==</d:DocumentBody>
Its "clone" has the correct subject and text, and also shows a test.txt attached which content is
‹ iS ÿI-.QH,)ILÎÈMÍ+áå ‹š=s
mimetype and filesize (while checking odata, I noticed that filesize is not actually correct!) appear to be correct (aka: the same as the original note I'm trying to copy), but OData seems to confirm something's off (it's different!):
<d:FileName>test.txt</d:FileName>
<d:FileSize m:type="Edm.Int32">60</d:FileSize
<d:DocumentBody>H4sIAED6aVMA/5Pv5mBg4MkMZvjP7amrF+iho+npc+6E71nth0+ZGLpn2RYLMjAwAABXqCwTJQAAAA==</d:DocumentBody>
The test.txt file was created from a command prompt (COPY CON test.txt, type, CTRL+Z).
I tried to change the file, and created a test.pdf through PDFCreator: AcroRead in turn whines and says the document is corrupted (so it seems like the issue is mimetype-agnostic).
I also tried re-implementing the same code through early binding (via the CRMSVCUTIL-generated classes) but it yields the exact same result (garbage instead of the attachment contents).
I attempted to hand-craft the documentbody like this:
// "VGVzdCBhdHRhY2htZW50" is Base64 for "Test attachment"
newNote.Attributes.Add("documentbody", "VGVzdCBhdHRhY2htZW50");
and the created file is correct.
I can't figure out what's going on: as far as I know, documentbody is supposed to be a Base64-encoded string which (again, as far as I know) shouldn't be any different when copied around. What am I missing ?
For reference, CRM is updated to UR13 but I repro'd it on a UR16 environment.
EDIT: Does NOT work (only for CRM 4)
Try this (not verified):
var notes = Service.RetrieveMultiple(notesQuery).Entities;
foreach (var newNote in notes)
{
newNote.annotationid = null;
newNote.Attributes.Add("objectid", new EntityReference("quote", sourceEntity.Id));
newNote.Attributes.Add("objecttypecode", "quote");
Service.Create(newNote);
}
Just saw this, in an article from MSDN:
Annotation setupAnnotation = new Annotation()
{
Subject = "Example Annotation",
FileName = "ExampleAnnotationAttachment.txt",
DocumentBody = Convert.ToBase64String(
new UnicodeEncoding().GetBytes("Sample Annotation Text")),
MimeType = "text/plain"
};
I see the document body is encoded according to the Unicode encoding. Maybe you should try to retrieve the encoding from the file and convert it to a string accordingly.

Modify programatically csproj files with Microsoft.Build.Evaluation (instead of Engine)

I would like to read, modify and write back csproj files.
I've found this code, but unfortunately Engine class is depreciated.
Engine engine = new Engine()
Project project = new Project(engine);
project.Load("myproject.csproj");
project.SetProperty("SignAssembly", "true");
project.Save("myproject.csproj");
So I've continued based on the hint I should use Evaluation.ProjectCollection instead of Engine:
var collection = new ProjectCollection();
collection.DefaultToolsVersion = "4.0";
var project = new Project(collection);
// project.Load("myproject.csproj") There is NO Load method :-(
project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// ... modify the project
project.Save(); // Interestingly there is a Save() method
There is no Load method anymore. I've tried to set the property FullPath, but the project still seems empty. Missed I something?
(Please note I do know that the .csproj file is a standard XML file with XSD schema and I know that we could read/write it by using XDocument or XmlDocument. That's a backup plan. Just seeing the .Save() method on the Project class I think I missed something if I can not load an existing .csproj. thx)
I've actually found the answer, hopefully will help others:
Instead of creating a new Project(...) and trying to .Load(...) it, we should use a factory method of the ProjectCollection class.
// Instead of:
// var project = new Project(collection);
// project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// use this:
var project = collection.LoadProject("myproject.csproj")
Since i can't comment:
This won't work in .net core without first setting the MSBuild.exe path variable. The code to do so can be found here
https://blog.rsuter.com/missing-sdk-when-using-the-microsoft-build-package-in-net-core/
and is written here
private static void SetMsBuildExePath()
{
try
{
var startInfo = new ProcessStartInfo("dotnet", "--list-sdks")
{
RedirectStandardOutput = true
};
var process = Process.Start(startInfo);
process.WaitForExit(1000);
var output = process.StandardOutput.ReadToEnd();
var sdkPaths = Regex.Matches(output, "([0-9]+.[0-9]+.[0-9]+) \\[(.*)\\]")
.OfType<Match>()
.Select(m => System.IO.Path.Combine(m.Groups[2].Value, m.Groups[1].Value, "MSBuild.dll"));
var sdkPath = sdkPaths.Last();
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", sdkPath);
}
catch (Exception exception)
{
Console.Write("Could not set MSBUILD_EXE_PATH: " + exception);
}
}

Creating an Ektron Smart Form Definition via the API

I've been trying to create a smart form definition from another application. The app successfully creates the smart form, but I'm unable to get the FieldList, DisplayXSLT or Schema fields to populate.
This leaves me with a blank smart form definition (less that ideal).
Here's the code I have to perform the action. Any ideas?
// form is a simple POCO with values copied from an existing SmartForm Definition
var config = new SmartFormConfigurationData();
config.SmartformTitle = form.Name;
config.SmartformDescription = form.Description;
config.XmlSchema = form.Schema;
config.PackageDisplayXslt = form.Xslt;
config.FieldList = form.FieldList;
config.Type = EkEnumeration.XmlConfigType.Content;
var api = new SmartFormConfigurationManager(ApiAccessMode.Admin);
api.RequestInformation.ServicesPath = this.EktronServiceHost;
api.RequestInformation.AuthenticationToken = this.GetAdminAuthToken();
api.Add(config);
Update:
I heard back from Ektron Support on this issue. It's not so much a "bug" per-se... It's more a case of this API class looking very similar to the ContentManager but not behaving like it. I expected that since it looked so similar to ContentManager and many of the other classes, I would be able to call Add() and it would just magically work. It turns out the solution is a little more complicated.
Adding a smartform is a two-step process: first Add(), then Update().
The Add method doesn't set all of the fields and in fact passes in NULL for a few of the parameters on the stored procedure that creates the entry in xml_collection_tbl.
The real fun comes in step 2. Basically, you start with the SmartForm's HTML -- the stuff you see when you're in the "Data Design" view for editing the smart form definition and you click that <> button ("HTML") at the bottom of the editor. You run that through a whole bunch of XSLTs that are burried in the WorkArea folder to construct the missing fields, and then you call update. Here's the code that worked for me:
var sfManager = new SmartFormConfigurationManager();
var data = sfManager.GetItem(12);
if (data == null) return;
var sfcData = new SmartFormConfigurationData();
sfcData.Type = EkEnumeration.XmlConfigType.Content;
sfcData.SmartformTitle = "SmartForm12 copy";
sfcData.SmartformDescription = "SmartForm12 copy";
sfcData.XmlSchema = "";
sfcData.PackageDisplayXslt = data.PackageDisplayXslt;
sfcData.FieldList = data.FieldList;
sfcData = sfManager.Add(sfcData);
Response.Write("SmartForm added with id: " + sfcData.Id);
var design = System.IO.File.ReadAllText(Server.MapPath("~/NewsArticleSmartForm.html"));
var contentApi = new ContentAPI();
var schema = contentApi.TransformXsltPackage(design, Server.MapPath("~/WorkArea/ContentDesigner/DesignToSchema.xslt"), true);
var fieldList = contentApi.TransformXsltPackage(design, Server.MapPath("~/WorkArea/ContentDesigner/DesignToFieldList.xslt"), true);
var viewEntryXslt = contentApi.TransformXsltPackage(design, Server.MapPath("~/WorkArea/ContentDesigner/DesignToEntryXSLT.xslt"), true);
var xsltArgs = new XsltArgumentList();
xsltArgs.AddParam("srcPath", "", "/WorkArea/ContentDesigner/");
var viewXsltSource = string.Concat("<root>", design, "<ektdesignpackage_list>", fieldList, "</ektdesignpackage_list></root>");
var viewXslt = contentApi.XSLTransform(viewXsltSource, Server.MapPath("~/WorkArea/ContentDesigner/DesignToViewXSLT.xslt"), true, false, xsltArgs, false);
var initialDocument = contentApi.TransformXsltPackage(design, Server.MapPath("~/WorkArea/ContentDesigner/PresentationToData.xslt"), true);
var sbPackage = new StringBuilder("<ektdesignpackage_forms><ektdesignpackage_form><ektdesignpackage_designs><ektdesignpackage_design>");
sbPackage.Append(design);
sbPackage.Append("</ektdesignpackage_design></ektdesignpackage_designs><ektdesignpackage_schemas><ektdesignpackage_schema>");
sbPackage.Append(schema);
sbPackage.Append("</ektdesignpackage_schema></ektdesignpackage_schemas><ektdesignpackage_lists><ektdesignpackage_list>");
sbPackage.Append(fieldList);
sbPackage.Append("</ektdesignpackage_list></ektdesignpackage_lists><ektdesignpackage_views><ektdesignpackage_view>");
sbPackage.Append(viewEntryXslt);
sbPackage.Append("</ektdesignpackage_view><ektdesignpackage_view>");
sbPackage.Append(viewXslt);
sbPackage.Append("</ektdesignpackage_view></ektdesignpackage_views><ektdesignpackage_initialDocuments><ektdesignpackage_initialDocument>");
sbPackage.Append(initialDocument);
sbPackage.Append("</ektdesignpackage_initialDocument></ektdesignpackage_initialDocuments></ektdesignpackage_form></ektdesignpackage_forms>");
sfcData.PackageXslt = sbPackage.ToString();
sfcData.FieldList = fieldList;
var baseFilename = "SmartForm" + sfcData.Id;
var schemaFilename = "/" + baseFilename + "Schema.xsd";
var xsltFilename = "/" + baseFilename + "Xslt.xslt";
sfcData.XmlSchema = schemaFilename; // The file will be prefixed with /XmlFiles
var unPackDisplayXslt = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:output method=\"xml\" version=\"1.0\" encoding=\"UTF-8\" indent=\"yes\"/><xsl:template match=\"/\"><xsl:choose><xsl:when test=\"ektdesignpackage_forms/ektdesignpackage_form[1]/ektdesignpackage_views/ektdesignpackage_view[2]\"><xsl:copy-of select=\"ektdesignpackage_forms/ektdesignpackage_form[1]/ektdesignpackage_views/ektdesignpackage_view[2]/node()\"/></xsl:when><xsl:otherwise><xsl:copy-of select=\"ektdesignpackage_forms/ektdesignpackage_form[1]/ektdesignpackage_views/ektdesignpackage_view[1]/node()\"/></xsl:otherwise></xsl:choose></xsl:template></xsl:stylesheet>";
var displayXslt = contentApi.TransformXsltPackage(sbPackage.ToString(), unPackDisplayXslt, false);
System.IO.File.WriteAllText(Server.MapPath("~/XmlFiles" + xsltFilename), displayXslt);
sfcData.Xslt1 = xsltFilename; // The file will be prefixed with /XmlFiles
sfcData.DefaultXslt = 1;
sfManager.Update(sfcData);
I extracted the HTML for my existing smart form and saved in the root of my site as NewsArticleSmartForm.html. Here's what my file looked like:
<p>Author: <input type="text" name="Author" id="Author" ektdesignns_caption="Author" ektdesignns_name="Author" title="Author" ektdesignns_indexed="false" ektdesignns_nodetype="element" size="24" class="design_textfield" /></p>
<p> </p>
<p>Article Summary: <textarea name="Summary" id="Summary" ektdesignns_caption="Summary" ektdesignns_name="Summary" title="Summary" ektdesignns_indexed="false" ektdesignns_nodetype="element" cols="40" rows="3" class="design_textfield"></textarea>
</p>
<p> </p>
<p>Article Body:</p>
<p> </p>
<ektdesignns_richarea id="Body" name="Body" ektdesignns_caption="Body" ektdesignns_name="Body" title="Body" ektdesignns_indexed="false" ektdesignns_nodetype="element"> </ektdesignns_richarea>
<p> </p>
Good luck!
Original Answer:
Creating a copy of a SmartForm configuration should be fairly straight-forward. The SmartFormConfigurationData object has a Clone() method on it which makes it really easy to create a copy of an existing SmartForm. I say "should be" because it doesn't work.
I had an answer all typed out ready to post; I tried some code and it appeared to work. The new smartform was listed in the workarea, but when I clicked on that new smartform to view its details, I realized something was wrong.
I tried the following code:
var sfManager = new SmartFormConfigurationManager();
var config = sfManager.GetItem(7);
if (config == null) return;
var newSmartForm = config.Clone();
newSmartForm.SmartformTitle += " copy";
sfManager.Add(newSmartForm);
Here are the details from the original smartform:
And here's what the new smartform looked like -- the one I created with the frameworkAPI:
I did find one API method that successfully created a copy of an existing smartform:
var sfApi = new Ektron.Cms.ContentAPI();
var newId = sfApi.ReplicateXmlConfiguration(7, "copied sf title");
The problem with that method is that the smartform must exist on your system and you can only change the title. So, I went digging into the database to see what was happening. It turns out that after calling this ReplicateXmlConfiguration() method, the two database records are identical (except for the expected LastUpdated type of fields).
After calling the frameworkAPI's Update() method (same holds true when calling Add()), the "updated" record is clearly different.
I think we've got a genuine bug here. This happens both in v8.7 sp2 and v9.0 sp1. I've opened a case with Ektron support, and I'll update my answer as I hear back. They have always been very responsive when I've dealt with them in the past.

Categories

Resources