How do I get a list of commits which contain a particular file, ie the equivalent of git log path for LibGit2Sharp.
Has it not been implemented or is there a way that I'm missing?
I was working on getting the same functionality into my application with LibGit2Sharp.
I wrote the code below which will list all of the commits that contain the file. The GitCommit class isn't included, but it is just a collection of properties.
My intention was to have the code only list commits where the file had changed, similar to a SVN log, but I haven't written that part yet.
Please note that the code hasn't been optimized, it was merely my initial attempt, but I hope it will be useful.
/// <summary>
/// Loads the history for a file
/// </summary>
/// <param name="filePath">Path to file</param>
/// <returns>List of version history</returns>
public List<IVersionHistory> LoadHistory(string filePath)
{
LibGit2Sharp.Repository repo = new Repository(this.pathToRepo);
string path = filePath.Replace(this.pathToRepo.Replace(System.IO.Path.DirectorySeparatorChar + ".git", string.Empty), string.Empty).Substring(1);
List<IVersionHistory> list = new List<IVersionHistory>();
foreach (Commit commit in repo.Head.Commits)
{
if (this.TreeContainsFile(commit.Tree, path) && list.Count(x => x.Date == commit.Author.When) == 0)
{
list.Add(new GitCommit() { Author = commit.Author.Name, Date = commit.Author.When, Message = commit.MessageShort} as IVersionHistory);
}
}
return list;
}
/// <summary>
/// Checks a GIT tree to see if a file exists
/// </summary>
/// <param name="tree">The GIT tree</param>
/// <param name="filename">The file name</param>
/// <returns>true if file exists</returns>
private bool TreeContainsFile(Tree tree, string filename)
{
if (tree.Any(x => x.Path == filename))
{
return true;
}
else
{
foreach (Tree branch in tree.Where(x => x.Type == GitObjectType.Tree).Select(x => x.Target as Tree))
{
if (this.TreeContainsFile(branch, filename))
{
return true;
}
}
}
return false;
}
LibGit2Sharp comes from the C library libgit2... which didn't include git log in the first place ;)
Yet, LibGit2Sharp has its own git log function:
Its page on git log involves Filters, but a Filter doesn't seem to filter by path (as detailed in "How to exclude stashes while querying refs?").
So it doesn't seem to be implemented at the moment.
Each time when tree/blob has been changed it gets new id hash.
All you need is to compare with parent commit tree/blob item hash:
var commits = repository.Commits
.Where(c => c.Parents.Count() == 1 && c.Tree["file"] != null &&
(c.Parents.FirstOrDefault().Tree["file"] == null ||
c.Tree["file"].Target.Id !=
c.Parents.FirstOrDefault().Tree["file"].Target.Id));
Very similar to dmck's answer but more up to date
private bool TreeContainsFile(Tree tree, string filePath)
{
//filePath is the relative path of your file to the root directory
if (tree.Any(x => x.Path == filePath))
{
return true;
}
return tree.Where(x => x.GetType() == typeof (TreeEntry))
.Select(x => x.Target)
.OfType<Tree>()
.Any(branch => TreeContainsFile(branch, filePath));
}
Related
I'm looking for a way to programmatically get the summary portion of Xml-comments of a method in ASP.net.
I have looked at the previous related posts and they do not supply a way of doing so in a web environment.
I can not use any 3rd party apps and due to a web environment, Visual studio plugin's aren't much use either.
The closest thing I have found to a working solution was the JimBlackler project, but it only works on DLL's.
Naturally, something like 'supply .CS file, get XML documentation' would be optimal.
Current situation
I have a web-service and trying to dynamically generate documentation for it.
Reading the Methods, and properties is easy, but getting the Summary for each method is throwing me off a bit.
/// <summary>
/// This Is what I'm trying to read
/// </summary>
public class SomeClass()
{
/// <summary>
/// This Is what I'm trying to read
/// </summary>
public void SomeMethod()
{
}
}
A Workaround - Using reflection on Program.DLL/EXE together with Program.XML file
If you take a look at the sibling .XML file generated by Visual Studio you will see that there is a fairly flat hierarchy of /members/member.
All you have to do is get hold on each method from your DLL via MethodInfo object. Once you have this object you turn to the XML and use XPATH to get the member containing the XML documentation for this method.
Members are preceded by a letter. XML doc for methods are preceded by "M:" for class by "T:" etc.
Load your sibling XML
string docuPath = dllPath.Substring(0, dllPath.LastIndexOf(".")) + ".XML";
if (File.Exists(docuPath))
{
_docuDoc = new XmlDocument();
_docuDoc.Load(docuPath);
}
Use this xpath to get the member representing the method XML docu
string path = "M:" + mi.DeclaringType.FullName + "." + mi.Name;
XmlNode xmlDocuOfMethod = _docuDoc.SelectSingleNode(
"//member[starts-with(#name, '" + path + "')]");
Now scan childnodes for all the rows of "///"
Sometimes the /// Summary contains extra blanks, if this bothers use this to remove
var cleanStr = Regex.Replace(row.InnerXml, #"\s+", " ");
The XML summary isn't stored in the .NET assembly - it's optionally written out to an XML file as part of your build (assuming you're using Visual Studio).
Consequently there is no way to "pull out" the XML summaries of each method via reflection on a compiled .NET assembly (either .EXE or .DLL) - because the data simply isn't there for you to pull out. If you want the data, you'll have to instruct your build environment to output the XML files as part of your build process and parse those XML files at runtime to get at the summary information.
You could 'document' your method using the System.ComponentModel.DataAnnotations.DisplayAttribute attribute, e.g.
[Display(Name = "Foo", Description = "Blah")]
void Foo()
{
}
then use reflection to pull the description at runtime.
A deleted post, made by #OleksandrIeremenko, on this thread links to this article https://jimblackler.net/blog/?p=49 which was the basis for my solution.
Below is a modification of Jim Blackler's code making extension methods off the MemberInfo and Type objects and adding code that returns the summary text or an empty string if not available.
Usage
var typeSummary = typeof([Type Name]).GetSummary();
var methodSummary = typeof([Type Name]).GetMethod("[Method Name]").GetSummary();
Extension Class
/// <summary>
/// Utility class to provide documentation for various types where available with the assembly
/// </summary>
public static class DocumentationExtensions
{
/// <summary>
/// Provides the documentation comments for a specific method
/// </summary>
/// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param>
/// <returns>The XML fragment describing the method</returns>
public static XmlElement GetDocumentation(this MethodInfo methodInfo)
{
// Calculate the parameter string as this is in the member name in the XML
var parametersString = "";
foreach (var parameterInfo in methodInfo.GetParameters())
{
if (parametersString.Length > 0)
{
parametersString += ",";
}
parametersString += parameterInfo.ParameterType.FullName;
}
//AL: 15.04.2008 ==> BUG-FIX remove “()” if parametersString is empty
if (parametersString.Length > 0)
return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")");
else
return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name);
}
/// <summary>
/// Provides the documentation comments for a specific member
/// </summary>
/// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param>
/// <returns>The XML fragment describing the member</returns>
public static XmlElement GetDocumentation(this MemberInfo memberInfo)
{
// First character [0] of member type is prefix character in the name in the XML
return XmlFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name);
}
/// <summary>
/// Returns the Xml documenation summary comment for this member
/// </summary>
/// <param name="memberInfo"></param>
/// <returns></returns>
public static string GetSummary(this MemberInfo memberInfo)
{
var element = memberInfo.GetDocumentation();
var summaryElm = element?.SelectSingleNode("summary");
if (summaryElm == null) return "";
return summaryElm.InnerText.Trim();
}
/// <summary>
/// Provides the documentation comments for a specific type
/// </summary>
/// <param name="type">Type to find the documentation for</param>
/// <returns>The XML fragment that describes the type</returns>
public static XmlElement GetDocumentation(this Type type)
{
// Prefix in type names is T
return XmlFromName(type, 'T', "");
}
/// <summary>
/// Gets the summary portion of a type's documenation or returns an empty string if not available
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetSummary(this Type type)
{
var element = type.GetDocumentation();
var summaryElm = element?.SelectSingleNode("summary");
if (summaryElm == null) return "";
return summaryElm.InnerText.Trim();
}
/// <summary>
/// Obtains the XML Element that describes a reflection element by searching the
/// members for a member that has a name that describes the element.
/// </summary>
/// <param name="type">The type or parent type, used to fetch the assembly</param>
/// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param>
/// <param name="name">Where relevant, the full name qualifier for the element</param>
/// <returns>The member that has a name that describes the specified reflection element</returns>
private static XmlElement XmlFromName(this Type type, char prefix, string name)
{
string fullName;
if (string.IsNullOrEmpty(name))
fullName = prefix + ":" + type.FullName;
else
fullName = prefix + ":" + type.FullName + "." + name;
var xmlDocument = XmlFromAssembly(type.Assembly);
var matchedElement = xmlDocument["doc"]["members"].SelectSingleNode("member[#name='" + fullName + "']") as XmlElement;
return matchedElement;
}
/// <summary>
/// A cache used to remember Xml documentation for assemblies
/// </summary>
private static readonly Dictionary<Assembly, XmlDocument> Cache = new Dictionary<Assembly, XmlDocument>();
/// <summary>
/// A cache used to store failure exceptions for assembly lookups
/// </summary>
private static readonly Dictionary<Assembly, Exception> FailCache = new Dictionary<Assembly, Exception>();
/// <summary>
/// Obtains the documentation file for the specified assembly
/// </summary>
/// <param name="assembly">The assembly to find the XML document for</param>
/// <returns>The XML document</returns>
/// <remarks>This version uses a cache to preserve the assemblies, so that
/// the XML file is not loaded and parsed on every single lookup</remarks>
public static XmlDocument XmlFromAssembly(this Assembly assembly)
{
if (FailCache.ContainsKey(assembly))
{
throw FailCache[assembly];
}
try
{
if (!Cache.ContainsKey(assembly))
{
// load the docuemnt into the cache
Cache[assembly] = XmlFromAssemblyNonCached(assembly);
}
return Cache[assembly];
}
catch (Exception exception)
{
FailCache[assembly] = exception;
throw;
}
}
/// <summary>
/// Loads and parses the documentation file for the specified assembly
/// </summary>
/// <param name="assembly">The assembly to find the XML document for</param>
/// <returns>The XML document</returns>
private static XmlDocument XmlFromAssemblyNonCached(Assembly assembly)
{
var assemblyFilename = assembly.Location;
if (!string.IsNullOrEmpty(assemblyFilename))
{
StreamReader streamReader;
try
{
streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename, ".xml"));
}
catch (FileNotFoundException exception)
{
throw new Exception("XML documentation not present (make sure it is turned on in project properties when building)", exception);
}
var xmlDocument = new XmlDocument();
xmlDocument.Load(streamReader);
return xmlDocument;
}
else
{
throw new Exception("Could not ascertain assembly filename", null);
}
}
}
You can use Namotion.Reflection NuGet package to get these information:
string summary = typeof(Foo).GetXmlDocsSummary();
You can look at https://github.com/NSwag/NSwag - source for nuget NSwag.CodeGeneration - it gets summary as well, usage
var generator = new WebApiAssemblyToSwaggerGenerator(settings);<br/>
var swaggerService = generator.GenerateForController("namespace.someController");<br/>
// string with comments <br/>
var swaggerJson = swaggerService.ToJson();
(try ILSPY decompiler against your dll, you check code and comments)
If you have access to the source code you're trying to get comments for, then you can use Roslyn compiler platform to do that. It basically gives you access to all the intermediary compiler metadata and you can do anything you want with it.
It's a bit more complicated than what other people are suggesting, but depending on what your needs are, might be an option.
It looks like this post has a code sample for something similar.
This question might be a little ambiguous so I'll explain my goal and my previous implementation. I'm looking for some advice on how to improve my implementation.
My application needs a certain set of days and times (hours and minutes) to be used for criteria later in the program.
The days and times are variable and depend on whether a user is a member of a particular group or not.
My previous implementation was to get the name of the group that was selected and then go to the web server and download the appropriate file which contained the day and time. There was a file for each group.
The format of the text file was:
Day,Day,Day,etc..HH,HH,MM,MM
It was then read into two separate arrays with the positions hardcoded. E.g. Index 0, 1,2 where days while 3,4 where hours and 5,6 where minutes.
This method also meant that I'd need a longer array for a group that had more days than another.
Obviously this was all very inefficient and the code wasn't very reusable or extendable. I'd have to alter if a new group was introduced and it had more of less data in the text file.
Edit - due to the vagueness of the question I have included code:
This method is passed the group name in the fileName parameter of CollectQualifyingTimes. The string looked like gtstimes.txt or gtsdates.txt or gectimes.txt or gecdates.txt
internal static class DownloadQualifyingTimes
{
//TimeDate Arrays
public static readonly List<string> QDaysList = new List<string>();
public static readonly List<int> QTimesList = new List<int>();
private static StreamReader _reader;
private static string _line;
private static string _file;
/// <summary>
/// Collects the Times
/// </summary>
public static void CollectQualifyingTimes(string fileName)
{
Logger.Debug("About to download the " + fileName + " Qualifying Times and Dates");
FileDownload.DownloadOnlineFile(fileName);
OpenQualifyingFile(fileName);
}
/// <summary>
/// Open the qualifying file and read the values.
/// </summary>
/// <returns></returns>
private static void OpenQualifyingFile(string fileName)
{
try
{
_file = Path + "\\" + fileName;
using (_reader = new StreamReader(_file))
{
while ((_line = _reader.ReadLine()) != null)
{
if (fileName.Contains("Times"))
{
QTimesList.Add(Convert.ToInt16(_line));
Logger.Debug("Times " + _line);
}
else
{
QDaysList.Add(_line);
Logger.Debug("Days " + _line);
}
}
}
}
catch (WebException exception)
{
Logger.Error(exception);
}
}
}
//The method that calls the Downloading class looks like this:
/// <summary>
///
/// </summary>
/// <param name="selectedLeague"></param>
/// <returns></returns>
public static bool QualificationTimeCheck(string selectedLeague)
{
var currentUtcTime = DateTime.UtcNow;
//Day check regardless of league
if (DownloadQualifyingTimes.QDaysList.Contains(currentUtcTime.DayOfWeek.ToString()))
{
Logger.Debug("Qualifying day condition meet");
if (selectedLeague.IsOneOf("GTS", "CAT"))
{
Logger.Debug("GTS or CAT qualifying logic");
if (currentUtcTime.Hour ==
DownloadQualifyingTimes.QTimesList[0] ||
currentUtcTime.Hour ==
DownloadQualifyingTimes.QTimesList[1])
{
Logger.Debug("Qualifying hour condition meet");
if (((currentUtcTime.Minute > DownloadQualifyingTimes.QTimesList[2])
&& (currentUtcTime.Minute < DownloadQualifyingTimes.QTimesList[3])) || SessionObject.LastLapStartedMinute <= DownloadQualifyingTimes.QTimesList[3])
{
Logger.Debug("Qualifying minute condition meet");
return true;
}
I hope this illustrates the nature of my question and the problem.
Can you think of a better way to implement this process? If you need any more information regarding it please don't hesitate to ask.
Edit - Ended up implementing List as per first comment suggestion.
How to Create nested folder in sharepoint through c# code ?
For Example, I have String like "Shared Documents/Folder1/Folder2/Folder3" and I want to create that folders or skip if folders are already exists, through c# code. Any suggestion or code is highly appreceated.
How to create nested Folder using SharePoint SSOM
internal static class SPFolderExtensions
{
/// <summary>
/// Ensure SPFolder
/// </summary>
/// <param name="web"></param>
/// <param name="listTitle"></param>
/// <param name="folderUrl"></param>
/// <returns></returns>
public static SPFolder CreateFolder(this SPWeb web, string listTitle, string folderUrl)
{
if (string.IsNullOrEmpty(folderUrl))
throw new ArgumentNullException("folderUrl");
var list = web.Lists.TryGetList(listTitle);
return CreateFolderInternal(list, list.RootFolder, folderUrl);
}
private static SPFolder CreateFolderInternal(SPList list, SPFolder parentFolder, string folderUrl)
{
var folderNames = folderUrl.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
var folderName = folderNames[0];
var curFolder =
parentFolder.SubFolders.Cast<SPFolder>()
.FirstOrDefault(
f =>
System.String.Compare(f.Name, folderName, System.StringComparison.OrdinalIgnoreCase) ==
0);
if (curFolder == null)
{
var folderItem = list.Items.Add(parentFolder.ServerRelativeUrl, SPFileSystemObjectType.Folder,
folderName);
folderItem.SystemUpdate();
curFolder = folderItem.Folder;
}
if (folderNames.Length > 1)
{
var subFolderUrl = string.Join("/", folderNames, 1, folderNames.Length - 1);
return CreateFolderInternal(list, curFolder, subFolderUrl);
}
return curFolder;
}
}
Key points:
Ability to create a nested folder(s)
Existing folders will not be affected
Usage
The following example demonstrates how to create the following folder structure under Documents library:
Orders
|
Orders A
|
Orders A1
Example:
var folder = web.CreateFolder("Documents", "Orders/Orders A/Orders A1");
In libgit2sharp https://github.com/libgit2/libgit2sharp/ how do you check for pending/uncommitted changes?
The following works for me:
///DEPRECATED - see comment from #derptastic
public bool HasUncommittedChanges
{
get
{
using (var repo = new Repository(repositoryRoot))
{
RepositoryStatus status = repo.RetrieveStatus();
return status.IsDirty;
}
}
}
Thanks to #Derptastic for the link to LibGit2Sharp Wiki
The following lines of code will provide the filename and the state of that file.
foreach (var item in repo1.RetrieveStatus())
{
Console.WriteLine(item.FilePath);
Console.WriteLine(item.State);
}
You can use repository.Diff.Compare().
/// <summary>
/// Show changes between the working directory and the index.
/// </summary>
/// <param name = "paths">The list of paths (either files or directories) that should be compared.</param>
/// <returns>A <see cref = "TreeChanges"/> containing the changes between the working directory and the index.</returns>
public virtual TreeChanges Compare(IEnumerable<string> paths = null)
Passing no paths at all should give all changes.
Using Sharepoint 2007 object model, I have been looking for an example in C# to move an item from one document library to another on the same server and saving the version history (i.e. SPListItemVersion objects) and metadata (the folders have the same content types, etc).
I was able to accomplish what I wanted to do with the following code:
/// <summary>
/// Adds item to archive
/// </summary>
/// <param name="item">Item to add</param>
/// <param name="destination">Archive path</param>
/// <param name="destination">web site of archive</param>
/// <returns>Result of arhivation process</returns>
public static string ArchiveItem(SPListItem item, string destination, SPWeb web)
{
// Save main meta information for later use:
var author = item.File.Author;
var modifiedBy = item.File.ModifiedBy;
var modified = item.File.TimeLastModified;
var created = item.File.TimeCreated;
// Get destination filename:
var destinationFile = destination + "/" + item.File.Name;
// Copy the item and set properties:
var coppiedFile = web.GetFolder(destination).Files.Add(
destinationFile,
item.File.OpenBinary(),
author,
modifiedBy,
created,
modified
);
coppiedFile.Item["Created"] = created;
coppiedFile.Item["Modified"] = modified;
// Save changes, UpdateOverwriteVersion causes object to save without saving a new version.
coppiedFile.Item.UpdateOverwriteVersion();
// If moving is enabled, delete original item:
item.Delete();
return coppiedFile.ServerRelativeUrl;
}