Esempio n. 1
0
        /// <summary>
        /// Parse the output of calling git ls-tree
        /// </summary>
        /// <param name="line">A line that was output from calling git ls-tree</param>
        /// <param name="repoRoot">The root path of the repo that the git ls-tree was ran against</param>
        /// <returns>A DiffTreeResult build from the output line</returns>
        /// <remarks>
        /// The call to ls-tree could be any of the following
        /// git ls-tree (treeish)
        /// git ls-tree -r (treeish)
        /// git ls-tree -t (treeish)
        /// git ls-tree -r -t (treeish)
        /// </remarks>
        public static DiffTreeResult ParseFromLsTreeLine(string line, string repoRoot)
        {
            if (string.IsNullOrEmpty(line))
            {
                throw new ArgumentException("Line to parse cannot be null or empty", nameof(line));
            }

            /*
             * Example output lines from ls-tree
             *
             * 040000 tree 73b881d52b607b0f3e9e620d36f556d3d233a11d\tGVFS
             * 100644 blob 44c5f5cba4b29d31c2ad06eed51ea02af76c27c0\tReadme.md
             * 100755 blob 196142fbb753c0a3c7c6690323db7aa0a11f41ec\tScripts/BuildGVFSForMac.sh
             * ^-mode ^-marker                                     ^-tab
             *             ^-sha                                     ^-path
             */

            // Everything from ls-tree is an add.
            int treeIndex = line.IndexOf(TreeMarker);

            if (treeIndex >= 0)
            {
                DiffTreeResult treeAdd = new DiffTreeResult();
                treeAdd.TargetIsDirectory = true;
                treeAdd.TargetPath        = AppendPathSeparatorIfNeeded(ConvertPathToAbsoluteUtf8Path(repoRoot, line.Substring(line.LastIndexOf("\t") + 1)));
                treeAdd.Operation         = DiffTreeResult.Operations.Add;

                return(treeAdd);
            }
            else
            {
                int blobIndex = line.IndexOf(BlobMarker);
                if (blobIndex >= 0)
                {
                    DiffTreeResult blobAdd = new DiffTreeResult();
                    blobAdd.TargetSha  = line.Substring(blobIndex + BlobMarker.Length, GVFSConstants.ShaStringLength);
                    blobAdd.TargetPath = ConvertPathToAbsoluteUtf8Path(repoRoot, line.Substring(line.LastIndexOf("\t") + 1));
                    blobAdd.Operation  = DiffTreeResult.Operations.Add;

                    return(blobAdd);
                }
                else
                {
                    return(null);
                }
            }
        }
Esempio n. 2
0
        public static DiffTreeResult ParseFromDiffTreeLine(string line, string repoRoot)
        {
            line = line.Substring(1);

            // Filenames may contain spaces, but always follow a \t. Other fields are space delimited.
            string[] parts = line.Split('\t');
            parts = parts[0].Split(' ').Concat(parts.Skip(1)).ToArray();

            DiffTreeResult result = new DiffTreeResult();

            result.SourceIsDirectory = ValidTreeModes.Contains(parts[0]);
            result.TargetIsDirectory = ValidTreeModes.Contains(parts[1]);
            result.SourceSha         = parts[2];
            result.TargetSha         = parts[3];
            result.Operation         = DiffTreeResult.ParseOperation(parts[4]);
            result.TargetFilename    = ConvertPathToAbsoluteUtf8Path(repoRoot, parts.Last());
            result.SourceFilename    = parts.Length == 7 ? ConvertPathToAbsoluteUtf8Path(repoRoot, parts[5]) : null;
            return(result);
        }
Esempio n. 3
0
        private void EnqueueOperationsFromLsTreeLine(string line)
        {
            DiffTreeResult result = DiffTreeResult.ParseFromLsTreeLine(line, this.enlistment.EnlistmentRoot);

            if (result == null)
            {
                this.tracer.RelatedError("Unrecognized ls-tree line: {0}", line);
            }

            if (!this.ResultIsInWhitelist(result))
            {
                return;
            }

            if (result.TargetIsDirectory)
            {
                this.DirectoryOperations.Enqueue(result);
            }
            else
            {
                this.EnqueueFileAddOperation(result);
            }
        }
Esempio n. 4
0
        /// <remarks>
        /// This is not used in a multithreaded method, it doesn't need to be thread-safe
        /// </remarks>
        private void EnqueueFileAddOperation(ITracer activity, DiffTreeResult operation)
        {
            // Each filepath should be case-insensitive unique. If there are duplicates, only the last parsed one should remain.
            if (!this.filesAdded.Add(operation.TargetFilename))
            {
                foreach (KeyValuePair <string, HashSet <string> > kvp in this.FileAddOperations)
                {
                    if (kvp.Value.Remove(operation.TargetFilename))
                    {
                        break;
                    }
                }
            }

            if (this.stagedFileDeletes.Remove(operation.TargetFilename))
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add("Filename", operation.TargetFilename);
                metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);
            }

            this.FileAddOperations.AddOrUpdate(
                operation.TargetSha,
                new HashSet <string>(StringComparer.OrdinalIgnoreCase)
            {
                operation.TargetFilename
            },
                (key, oldValue) =>
            {
                oldValue.Add(operation.TargetFilename);
                return(oldValue);
            });

            this.RequiredBlobs.Add(operation.TargetSha);
        }
Esempio n. 5
0
 private bool ResultIsInWhitelist(DiffTreeResult blobAdd)
 {
     return(blobAdd.TargetFilename == null ||
            this.pathWhitelist.Count == 0 ||
            this.pathWhitelist.Any(path => blobAdd.TargetFilename.StartsWith(path, StringComparison.OrdinalIgnoreCase)));
 }
Esempio n. 6
0
        private void EnqueueOperationsFromDiffTreeLine(ITracer activity, string repoRoot, string line)
        {
            if (!line.StartsWith(":"))
            {
                // Diff-tree starts with metadata we can ignore.
                // Real diff lines always start with a colon
                return;
            }

            DiffTreeResult result = DiffTreeResult.ParseFromDiffTreeLine(line, repoRoot);

            if (!this.ResultIsInWhitelist(result))
            {
                return;
            }

            if (result.Operation == DiffTreeResult.Operations.Unknown ||
                result.Operation == DiffTreeResult.Operations.Unmerged)
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add("Path", result.TargetFilename);
                metadata.Add("ErrorMessage", "Unexpected diff operation: " + result.Operation);
                activity.RelatedError(metadata);
                this.HasFailures = true;
                return;
            }

            // Separate and enqueue all directory operations first.
            if (result.SourceIsDirectory || result.TargetIsDirectory)
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Filename", result.TargetFilename);
                        metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);
                    }

                    break;

                case DiffTreeResult.Operations.RenameEdit:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        // This could happen if a directory was deleted and an existing directory was renamed to replace it, but with a different case.
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Filename", result.TargetFilename);
                        metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);

                        // The target of RenameEdit is always akin to an Add, so replacing the delete is the safer thing to do.
                        this.stagedDirectoryOperations.Remove(result);
                        this.stagedDirectoryOperations.Add(result);
                    }

                    if (!result.TargetIsDirectory)
                    {
                        // Handle when a directory becomes a file.
                        // Files becoming directories is handled by HandleAllDirectoryOperations
                        this.EnqueueFileAddOperation(activity, result);
                    }

                    break;

                case DiffTreeResult.Operations.Add:
                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.CopyEdit:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Filename", result.TargetFilename);
                        metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);

                        // Replace the delete with the add to make sure we don't delete a folder from under ourselves
                        this.stagedDirectoryOperations.Remove(result);
                        this.stagedDirectoryOperations.Add(result);
                    }

                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
            else
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    this.EnqueueFileDeleteOperation(activity, result.TargetFilename);

                    break;

                case DiffTreeResult.Operations.RenameEdit:

                    this.EnqueueFileAddOperation(activity, result);
                    this.EnqueueFileDeleteOperation(activity, result.SourceFilename);

                    break;

                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.CopyEdit:
                case DiffTreeResult.Operations.Add:
                    this.EnqueueFileAddOperation(activity, result);
                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
        }
Esempio n. 7
0
        public static DiffTreeResult ParseFromDiffTreeLine(string line)
        {
            if (string.IsNullOrEmpty(line))
            {
                throw new ArgumentException("Line to parse cannot be null or empty", nameof(line));
            }

            /*
             * The lines passed to this method should be the result of a call to git diff-tree -r -t (sourceTreeish) (targetTreeish)
             *
             * Example output lines from git diff-tree
             * :000000 040000 0000000000000000000000000000000000000000 cee82f9d431bf610404f67bcdda3fee76f0c1dd5 A\tGVFS/FastFetch/Git
             * :000000 100644 0000000000000000000000000000000000000000 cdc036f9d561f14d908e0a0c337105b53c778e5e A\tGVFS/FastFetch/Git/FastFetchGitObjects.cs
             * :040000 000000 f68b90da732791438d67c0326997a2d26e4c2de4 0000000000000000000000000000000000000000 D\tGVFS/GVFS.CLI
             * :100644 000000 1242fc97c612ff286a5f1221d569508600ca5e06 0000000000000000000000000000000000000000 D\tGVFS/GVFS.CLI/GVFS.CLI.csproj
             * :040000 040000 3823348f91113a619eed8f48fe597cc9c7d088d8 fd56ff77b12a0b76567cb55ed4950272eac8b8f6 M\tGVFS/GVFS.Common
             * :100644 100644 57d9c737c8a48632cfbb12cae00c97d512b9f155 524d7dbcebd33e4007c52711d3f21b17373de454 M\tGVFS/GVFS.Common/GVFS.Common.csproj
             *  ^-[0]  ^-[1]  ^-[2]                                    ^-[3]                                    ^-[4]
             *                                                                                                   ^-tab
             *                                                                                                     ^-[5]
             *
             * This output will only happen if -C or -M is passed to the diff-tree command
             * Since we are not passing those options we shouldn't have to handle this format.
             * :100644 100644 3ac7d60a25bb772af1d5843c76e8a070c062dc5d c31a95125b8a6efd401488839a7ed1288ce01634 R094\tGVFS/GVFS.CLI/CommandLine/CloneVerb.cs\tGVFS/GVFS/CommandLine/CloneVerb.cs
             */

            if (!line.StartsWith(":"))
            {
                throw new ArgumentException($"diff-tree lines should start with a :", nameof(line));
            }

            // Skip the colon at the front
            line = line.Substring(1);

            // Filenames may contain spaces, but always follow a \t. Other fields are space delimited.
            // Splitting on \t will give us the mode, sha, operation in parts[0] and that path in parts[1] and optionally in paths[2]
            string[] parts = line.Split(new[] { '\t' }, count: 2);

            // Take the mode, sha, operation part and split on a space then add the paths that were split on a tab to the end
            parts = parts[0].Split(' ').Concat(parts.Skip(1)).ToArray();

            if (parts.Length != 6 ||
                parts[5].Contains('\t'))
            {
                // Look at file history to see how -C -M with 7 parts could be handled
                throw new ArgumentException($"diff-tree lines should have 6 parts unless passed -C or -M which this method doesn't handle", nameof(line));
            }

            DiffTreeResult result = new DiffTreeResult();

            result.SourceIsDirectory = ValidTreeModes.Contains(parts[0]);
            result.TargetIsDirectory = ValidTreeModes.Contains(parts[1]);
            result.SourceMode        = Convert.ToUInt16(parts[0], 8);
            result.TargetMode        = Convert.ToUInt16(parts[1], 8);

            if (!result.TargetIsDirectory)
            {
                result.TargetIsSymLink = result.TargetMode == SymLinkFileIndexEntry;
            }

            result.SourceSha  = parts[2];
            result.TargetSha  = parts[3];
            result.Operation  = DiffTreeResult.ParseOperation(parts[4]);
            result.TargetPath = ConvertPathToUtf8Path(parts[5]);
            if (result.TargetIsDirectory || result.SourceIsDirectory)
            {
                // Since diff-tree is not doing rename detection, file->directory or directory->file transformations are always multiple lines
                // with a delete line and an add line
                // :000000 040000 0000000000000000000000000000000000000000 cee82f9d431bf610404f67bcdda3fee76f0c1dd5 A\tGVFS/FastFetch/Git
                // :040000 040000 3823348f91113a619eed8f48fe597cc9c7d088d8 fd56ff77b12a0b76567cb55ed4950272eac8b8f6 M\tGVFS/GVFS.Common
                // :040000 000000 f68b90da732791438d67c0326997a2d26e4c2de4 0000000000000000000000000000000000000000 D\tGVFS/GVFS.CLI
                result.TargetPath = AppendPathSeparatorIfNeeded(result.TargetPath);
            }

            return(result);
        }
Esempio n. 8
0
        private void EnqueueOperationsFromDiffTreeLine(ITracer activity, string repoRoot, string line)
        {
            if (!line.StartsWith(":"))
            {
                // Diff-tree starts with metadata we can ignore.
                // Real diff lines always start with a colon
                return;
            }

            DiffTreeResult result = DiffTreeResult.ParseFromDiffTreeLine(line, repoRoot);

            if (!this.ResultIsInWhitelist(result))
            {
                return;
            }

            if (result.Operation == DiffTreeResult.Operations.Unknown ||
                result.Operation == DiffTreeResult.Operations.Unmerged)
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add("Path", result.TargetFilename);
                metadata.Add("ErrorMessage", "Unexpected diff operation: " + result.Operation);
                activity.RelatedError(metadata);
                this.HasFailures = true;
                return;
            }

            if (result.Operation == DiffTreeResult.Operations.Delete)
            {
                // Don't enqueue deletes that will be handled by recursively deleting their parent.
                // Git traverses diffs in pre-order, so we are guaranteed to ignore child deletes here.
                // Append trailing slash terminator to avoid matches with directory prefixes (Eg. \GVFS and \GVFS.Common)
                string pathWithSlash = result.TargetFilename + "\\";
                if (this.deletedPaths.Any(path => pathWithSlash.StartsWith(path, StringComparison.OrdinalIgnoreCase)))
                {
                    if (result.SourceIsDirectory || result.TargetIsDirectory)
                    {
                        Interlocked.Increment(ref this.additionalDirDeletes);
                    }
                    else
                    {
                        Interlocked.Increment(ref this.additionalFileDeletes);
                    }

                    return;
                }

                this.deletedPaths.Add(pathWithSlash);
            }

            // Separate and enqueue all directory operations first.
            if (result.SourceIsDirectory || result.TargetIsDirectory)
            {
                // Handle when a directory becomes a file.
                // Files becoming directories is handled by HandleAllDirectoryOperations
                if (result.Operation == DiffTreeResult.Operations.RenameEdit &&
                    !result.TargetIsDirectory)
                {
                    this.EnqueueFileAddOperation(result);
                }

                this.DirectoryOperations.Enqueue(result);
            }
            else
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    this.FileDeleteOperations.Enqueue(result.TargetFilename);
                    break;

                case DiffTreeResult.Operations.RenameEdit:
                    this.FileDeleteOperations.Enqueue(result.SourceFilename);
                    this.EnqueueFileAddOperation(result);
                    break;

                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.CopyEdit:
                case DiffTreeResult.Operations.Add:
                    this.EnqueueFileAddOperation(result);
                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
        }