public static bool TryGetFromObjectFile(string filePath, out GitCommitObject commitObject) { commitObject = default; try { using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { // We skip the 2 bytes zlib header magic number. fs.Seek(2, SeekOrigin.Begin); using (var defStream = new DeflateStream(fs, CompressionMode.Decompress)) { byte[] buffer = new byte[8192]; int readBytes = defStream.Read(buffer, 0, buffer.Length); defStream.Close(); if (_commitByteArray.SequenceEqual(buffer.Take(_commitByteArray.Length))) { string strContent = Encoding.UTF8.GetString(buffer, 0, readBytes); string dataContent = strContent.Substring(strContent.IndexOf('\0') + 1); commitObject = new GitCommitObject(dataContent); return(true); } } } } catch (Exception ex) { Log.Error(ex, "Error getting commit object from object file"); } return(false); }
public static bool TryGetFromPackageOffset(GitPackageOffset packageOffset, out GitCommitObject commitObject) { commitObject = default; string packFile = Path.ChangeExtension(packageOffset.FilePath, ".pack"); if (File.Exists(packFile)) { // packfile format explanation: // https://codewords.recurse.com/issues/three/unpacking-git-packfiles#:~:text=idx%20file%20contains%20the%20index,pack%20file.&text=Objects%20in%20a%20packfile%20can,of%20storing%20the%20whole%20object. using (var fs = new FileStream(packFile, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var br = new BigEndianBinaryReader(fs)) { // Move to the offset of the object fs.Seek(packageOffset.Offset, SeekOrigin.Begin); int objectSize; byte[] packData = br.ReadBytes(2); if (packData[0] < 128) { objectSize = (int)(packData[0] & 0x0f); packData = br.ReadBytes(objectSize); } else { objectSize = (((ushort)(packData[1] & 0x7f)) * 16) + ((ushort)(packData[0] & 0x0f)); packData = br.ReadBytes(objectSize * 100); } using (var ms = new MemoryStream(packData, 2, packData.Length - 2)) using (var defStream = new DeflateStream(ms, CompressionMode.Decompress)) { byte[] buffer = new byte[8192]; int readBytes = defStream.Read(buffer, 0, buffer.Length); defStream.Close(); string strContent = Encoding.UTF8.GetString(buffer, 0, readBytes); commitObject = new GitCommitObject(strContent); return(true); } } } return(false); }
public static bool TryGetFromPackageOffset(GitPackageOffset packageOffset, out GitCommitObject commitObject) { commitObject = default; try { string packFile = Path.ChangeExtension(packageOffset.FilePath, ".pack"); if (File.Exists(packFile)) { // packfile format explanation: // https://codewords.recurse.com/issues/three/unpacking-git-packfiles#:~:text=idx%20file%20contains%20the%20index,pack%20file.&text=Objects%20in%20a%20packfile%20can,of%20storing%20the%20whole%20object. using (var fs = new FileStream(packFile, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var br = new BigEndianBinaryReader(fs)) { // Move to the offset of the object fs.Seek(packageOffset.Offset, SeekOrigin.Begin); byte[] packData = br.ReadBytes(2); // Extract the object size (https://codewords.recurse.com/images/three/varint.svg) int objectSize = (int)(packData[0] & 0x0F); if (packData[0] >= 128) { int shift = 4; objectSize += (packData[1] & 0x7F) << shift; if (packData[1] >= 128) { byte pData; do { shift += 7; pData = br.ReadByte(); objectSize += (pData & 0x7F) << shift; }while (pData >= 128); } } // Check if the object size is in the aceptable range if (objectSize > 0 && objectSize < ushort.MaxValue) { // Advance 2 bytes to skip the zlib magic number uint zlibMagicNumber = br.ReadUInt16(); if ((byte)zlibMagicNumber == 0x78) { // Read the git commit object using (var defStream = new DeflateStream(br.BaseStream, CompressionMode.Decompress)) { byte[] buffer = new byte[objectSize]; int readBytes = defStream.Read(buffer, 0, buffer.Length); defStream.Close(); string strContent = Encoding.UTF8.GetString(buffer, 0, readBytes); commitObject = new GitCommitObject(strContent); return(true); } } else { Log.Warning("The commit data doesn't have a valid zlib header magic number."); } } else { Log.Warning <int>("The object size is outside of an acceptable range: {objectSize}", objectSize); } } } } catch (Exception ex) { Log.Error(ex, "Error loading commit information from package offset"); } return(false); }
private static GitInfo GetFrom(DirectoryInfo gitDirectory) { if (gitDirectory == null) { return(new GitInfo()); } GitInfo gitInfo = new GitInfo(); try { gitInfo.SourceRoot = gitDirectory.Parent?.FullName; // Get Git commit string headPath = Path.Combine(gitDirectory.FullName, "HEAD"); if (File.Exists(headPath)) { string head = File.ReadAllText(headPath).Trim(); // Symbolic Reference if (head.StartsWith("ref:")) { gitInfo.Branch = head.Substring(4).Trim(); string refPath = Path.Combine(gitDirectory.FullName, gitInfo.Branch); string infoRefPath = Path.Combine(gitDirectory.FullName, "info", "refs"); if (File.Exists(refPath)) { // Get the commit from the .git/{refPath} file. gitInfo.Commit = File.ReadAllText(refPath).Trim(); } else if (File.Exists(infoRefPath)) { // Get the commit from the .git/info/refs file. string[] lines = File.ReadAllLines(infoRefPath); foreach (string line in lines) { string[] hashRef = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (hashRef[1] == gitInfo.Branch) { gitInfo.Commit = hashRef[0]; } } } } else { // Hash reference gitInfo.Commit = head; } } // Process Git Config string configPath = Path.Combine(gitDirectory.FullName, "config"); List <ConfigItem> lstConfigs = GetConfigItems(configPath); if (lstConfigs != null && lstConfigs.Count > 0) { var remote = "origin"; var branchItem = lstConfigs.Find(i => i.Type == "branch" && i.Merge == gitInfo.Branch); if (branchItem != null) { gitInfo.Branch = branchItem.Name; remote = branchItem.Remote; } var remoteItem = lstConfigs.Find(i => i.Type == "remote" && i.Name == remote); if (remoteItem != null) { gitInfo.Repository = remoteItem.Url; } } // Get author and committer data if (!string.IsNullOrEmpty(gitInfo.Commit)) { string folder = gitInfo.Commit.Substring(0, 2); string file = gitInfo.Commit.Substring(2); string objectFilePath = Path.Combine(gitDirectory.FullName, "objects", folder, file); if (File.Exists(objectFilePath)) { // Load and parse object file if (GitCommitObject.TryGetFromObjectFile(objectFilePath, out var commitObject)) { gitInfo.AuthorDate = commitObject.AuthorDate; gitInfo.AuthorEmail = commitObject.AuthorEmail; gitInfo.AuthorName = commitObject.AuthorName; gitInfo.CommitterDate = commitObject.CommitterDate; gitInfo.CommitterEmail = commitObject.CommitterEmail; gitInfo.CommitterName = commitObject.CommitterName; gitInfo.Message = commitObject.Message; gitInfo.PgpSignature = commitObject.PgpSignature; } } else { // Search git object file from the pack files string packFolder = Path.Combine(gitDirectory.FullName, "objects", "pack"); string[] files = Directory.GetFiles(packFolder, "*.idx", SearchOption.TopDirectoryOnly); foreach (string idxFile in files) { if (GitPackageOffset.TryGetPackageOffset(idxFile, gitInfo.Commit, out var packageOffset)) { if (GitCommitObject.TryGetFromPackageOffset(packageOffset, out var commitObject)) { gitInfo.AuthorDate = commitObject.AuthorDate; gitInfo.AuthorEmail = commitObject.AuthorEmail; gitInfo.AuthorName = commitObject.AuthorName; gitInfo.CommitterDate = commitObject.CommitterDate; gitInfo.CommitterEmail = commitObject.CommitterEmail; gitInfo.CommitterName = commitObject.CommitterName; gitInfo.Message = commitObject.Message; gitInfo.PgpSignature = commitObject.PgpSignature; break; } } } } } } catch (Exception ex) { Log.Error(ex, "Error loading git information from directory"); } return(gitInfo); }