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);
            }
Exemplo n.º 2
0
            public static bool TryGetPackageOffset(string idxFilePath, string commitSha, out GitPackageOffset packageOffset)
            {
                packageOffset = default;

                // 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.

                string index         = commitSha.Substring(0, 2);
                int    folderIndex   = int.Parse(index, System.Globalization.NumberStyles.HexNumber);
                int    previousIndex = folderIndex > 0 ? folderIndex - 1 : folderIndex;

                try
                {
                    using (var fs = new FileStream(idxFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                        using (var br = new BigEndianBinaryReader(fs))
                        {
                            // Skip header and version
                            fs.Seek(8, SeekOrigin.Begin);

                            // First layer: 256 4-byte elements, with number of elements per folder
                            uint numberOfObjectsInPreviousIndex = 0;
                            if (previousIndex > -1)
                            {
                                // Seek to previous index position and read the number of objects
                                fs.Seek(previousIndex * 4, SeekOrigin.Current);
                                numberOfObjectsInPreviousIndex = br.ReadUInt32();
                            }

                            // In the fanout table, every index has its objects + the previous ones.
                            // We need to subtract the previous index objects to know the correct
                            // actual number of objects for this specific index.
                            uint numberOfObjectsInIndex = br.ReadUInt32() - numberOfObjectsInPreviousIndex;

                            // Seek to last position. The last position contains the number of all objects.
                            fs.Seek((255 - (folderIndex + 1)) * 4, SeekOrigin.Current);
                            uint totalNumberOfObjects = br.ReadUInt32();

                            // Second layer: 20-byte elements with the names in order
                            // Search the sha index in the second layer: the SHA listing.
                            uint?indexOfCommit = null;
                            fs.Seek(20 * (int)numberOfObjectsInPreviousIndex, SeekOrigin.Current);
                            for (uint i = 0; i < numberOfObjectsInIndex; i++)
                            {
                                string str = BitConverter.ToString(br.ReadBytes(20)).Replace("-", string.Empty);
                                if (str.Equals(commitSha, StringComparison.OrdinalIgnoreCase))
                                {
                                    indexOfCommit = numberOfObjectsInPreviousIndex + i;

                                    // If we find the SHA, we skip all SHA listing table.
                                    fs.Seek(20 * (totalNumberOfObjects - (indexOfCommit.Value + 1)), SeekOrigin.Current);
                                    break;
                                }
                            }

                            if (indexOfCommit.HasValue)
                            {
                                // Third layer: 4 byte CRC for each object. We skip it
                                fs.Seek(4 * totalNumberOfObjects, SeekOrigin.Current);

                                uint indexOfCommitValue = indexOfCommit.Value;

                                // Fourth layer: 4 byte per object of offset in pack file
                                fs.Seek(4 * indexOfCommitValue, SeekOrigin.Current);
                                uint offset = br.ReadUInt32();

                                ulong packOffset;
                                if (((offset >> 31) & 1) == 0)
                                {
                                    // offset is in the layer
                                    packOffset = (ulong)offset;
                                }
                                else
                                {
                                    // offset is not in this layer, clear first bit and look at it at the 5th layer
                                    offset &= 0x7FFFFFFF;
                                    // Skip complete fourth layer.
                                    fs.Seek(4 * (totalNumberOfObjects - (indexOfCommitValue + 1)), SeekOrigin.Current);
                                    // Use the offset from fourth layer, to find the actual pack file offset in the fifth layer.
                                    // In this case, the offset is 8 bytes long.
                                    fs.Seek(8 * offset, SeekOrigin.Current);
                                    packOffset = br.ReadUInt64();
                                }

                                packageOffset = new GitPackageOffset(idxFilePath, (long)packOffset);
                                return(true);
                            }
                        }
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "Error getting package offset");
                }

                return(false);
            }
Exemplo n.º 3
0
            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);
            }
Exemplo n.º 4
0
        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);
        }
            public static bool TryGetPackageOffset(string idxFilePath, string commitSha, out GitPackageOffset packageOffset)
            {
                packageOffset = default;

                // 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.

                string index         = commitSha.Substring(0, 2);
                int    folderIndex   = int.Parse(index, System.Globalization.NumberStyles.HexNumber);
                int    previousIndex = folderIndex > 0 ? folderIndex - 1 : folderIndex;

                using (var fs = new FileStream(idxFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    using (var br = new BigEndianBinaryReader(fs))
                    {
                        fs.Seek(8, SeekOrigin.Begin);

                        // First layer: 256 4-byte elements, with number of elements per folder
                        fs.Seek(previousIndex * 4, SeekOrigin.Current);
                        var numberOfPreviousObjects = br.ReadUInt32();
                        var numberOfObjectsInIndex  = br.ReadUInt32() - numberOfPreviousObjects;
                        fs.Seek(-8, SeekOrigin.Current);

                        fs.Seek((255 - previousIndex) * 4, SeekOrigin.Current);
                        var totalNumberOfObjects = br.ReadUInt32();

                        // Second layer: 20-byte elements with the names in order
                        fs.Seek(20 * (int)numberOfPreviousObjects, SeekOrigin.Current);
                        uint?indexOfCommit = null;
                        for (uint i = 0; i < numberOfObjectsInIndex; i++)
                        {
                            var str = BitConverter.ToString(br.ReadBytes(20)).Replace("-", string.Empty);
                            if (str.Equals(commitSha, StringComparison.OrdinalIgnoreCase))
                            {
                                indexOfCommit = numberOfPreviousObjects + i;
                                fs.Seek(-20, SeekOrigin.Current);
                                break;
                            }
                        }

                        if (indexOfCommit.HasValue)
                        {
                            uint indexOfObject = indexOfCommit.Value;
                            fs.Seek(20 * (totalNumberOfObjects - indexOfObject), SeekOrigin.Current);

                            // Third layer: 4 byte CRC for each object. We skip it
                            fs.Seek(4 * totalNumberOfObjects, SeekOrigin.Current);

                            // Fourth layer: 4 byte per object of offset in pack file
                            fs.Seek(4 * indexOfObject, SeekOrigin.Current);
                            var offset = br.ReadUInt32();
                            fs.Seek(-4, SeekOrigin.Current);

                            ulong packOffset;
                            if ((offset & 0x8000000) == 0)
                            {
                                // offset is in the layer
                                packOffset = (ulong)offset;
                            }
                            else
                            {
                                // offset is not in this layer, clear first bit and look at it at the 5th layer
                                offset = offset & 0x7FFFFFFF;
                                fs.Seek(4 * (totalNumberOfObjects - indexOfObject), SeekOrigin.Current);
                                fs.Seek(8 * indexOfObject, SeekOrigin.Current);
                                packOffset = br.ReadUInt64();
                                fs.Seek(-8, SeekOrigin.Current);
                            }

                            packageOffset = new GitPackageOffset(idxFilePath, (long)packOffset);
                            return(true);
                        }
                    }

                return(false);
            }