Ejemplo n.º 1
0
        // Merges the fetched changes
        private void Rebase()
        {
            if (HasLocalChanges)
            {
                Add();

                string commit_message = FormatCommitMessage();
                Commit(commit_message);
            }

            SparkleGit git = new SparkleGit(LocalPath, "rebase FETCH_HEAD");

            git.StartInfo.RedirectStandardOutput = false;
            git.StartAndWaitForExit();

            if (git.ExitCode != 0)
            {
                SparkleLogger.LogInfo("Git", Name + " | Conflict detected, trying to get out...");

                while (HasLocalChanges)
                {
                    try {
                        ResolveConflict();
                    } catch (IOException e) {
                        SparkleLogger.LogInfo("Git",
                                              Name + " | Failed to resolve conflict, trying again... (" + e.Message + ")");
                    }
                }

                SparkleLogger.LogInfo("Git", Name + " | Conflict resolved");
                OnConflictResolved();
            }
        }
Ejemplo n.º 2
0
        public override void RevertFile(string path, string revision)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            if (revision == null)
            {
                throw new ArgumentNullException("revision");
            }

            path = path.Replace("\\", "/");

            SparkleGit git = new SparkleGit(LocalPath, "checkout " + revision + " \"" + path + "\"");

            git.StartAndWaitForExit();

            if (git.ExitCode == 0)
            {
                SparkleLogger.LogInfo("Git", Name + " | Checked out \"" + path + "\" (" + revision + ")");
            }
            else
            {
                SparkleLogger.LogInfo("Git", Name + " | Failed to check out \"" + path + "\" (" + revision + ")");
            }
        }
Ejemplo n.º 3
0
        public override void EnableFetchedRepoCrypto(string password)
        {
            // Set up the encryption filter
            SparkleGit git_config_smudge = new SparkleGit(TargetFolder,
                                                          "config filter.encryption.smudge \"openssl enc -d -aes-256-cbc -base64 -S " + this.crypto_salt +
                                                          " -pass file:.git/info/encryption_password\"");

            SparkleGit git_config_clean = new SparkleGit(TargetFolder,
                                                         "config filter.encryption.clean  \"openssl enc -e -aes-256-cbc -base64 -S " + this.crypto_salt +
                                                         " -pass file:.git/info/encryption_password\"");

            git_config_smudge.StartAndWaitForExit();
            git_config_clean.StartAndWaitForExit();

            // Pass all files through the encryption filter
            string git_attributes_file_path = new string [] { TargetFolder, ".git", "info", "attributes" }.Combine();

            File.WriteAllText(git_attributes_file_path, "\n* filter=encryption");

            // Store the password
            string password_file_path = new string [] { TargetFolder, ".git", "info", "encryption_password" }.Combine();

            if (this.crypto_password_is_hashed)
            {
                File.WriteAllText(password_file_path, password.SHA256(this.crypto_salt));
            }
            else
            {
                File.WriteAllText(password_file_path, password);
            }
        }
Ejemplo n.º 4
0
        private void InstallConfiguration()
        {
            string [] settings = new string [] {
                "core.quotepath false",        // Don't quote "unusual" characters in path names
                "core.ignorecase false",       // Be case sensitive explicitly to work on Mac
                "core.filemode false",         // Ignore permission changes
                "core.autocrlf false",         // Don't change file line endings
                "core.precomposeunicode true", // Use the same Unicode form on all filesystems
                "core.safecrlf false",
                "core.excludesfile \"\"",
                "core.packedGitLimit 128m", // Some memory limiting options
                "core.packedGitWindowSize 128m",
                "pack.deltaCacheSize 128m",
                "pack.packSizeLimit 128m",
                "pack.windowMemory 128m",
                "push.default matching"
            };

            foreach (string setting in settings)
            {
                SparkleGit git_config = new SparkleGit(TargetFolder, "config " + setting);
                git_config.StartAndWaitForExit();
            }

            if (this.use_git_bin)
            {
                InstallGitBinConfiguration();
            }
        }
Ejemplo n.º 5
0
        public SparkleRepo(string path, SparkleConfig config) : base(path, config)
        {
            SparkleGit git = new SparkleGit(LocalPath, "config core.ignorecase false");

            git.StartAndWaitForExit();

            // Check if we should use git-bin
            git = new SparkleGit(LocalPath, "config --get filter.bin.clean");
            git.StartAndWaitForExit();

            this.use_git_bin = (git.ExitCode == 0);

            if (this.use_git_bin)
            {
                ConfigureGitBin();
            }

            git = new SparkleGit(LocalPath, "config remote.origin.url \"" + RemoteUrl + "\"");
            git.StartAndWaitForExit();

            string password_file_path = Path.Combine(LocalPath, ".git", "password");

            if (File.Exists(password_file_path))
            {
                this.is_encrypted = true;
            }
        }
Ejemplo n.º 6
0
        // Stages the made changes
        private bool Add()
        {
            SparkleGit git = new SparkleGit(LocalPath, "add --all");

            git.StartAndWaitForExit();

            return(git.ExitCode == 0);
        }
Ejemplo n.º 7
0
        // Stages the made changes
        private void Add()
        {
            SparkleGit git = new SparkleGit(LocalPath, "add --all");

            git.StartAndWaitForExit();

            SparkleLogger.LogInfo("Git", Name + " | Changes staged");
        }
Ejemplo n.º 8
0
        public override void RestoreFile(string path, string revision, string target_file_path)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            if (revision == null)
            {
                throw new ArgumentNullException("revision");
            }

            SparkleLogger.LogInfo("Git", Name + " | Restoring \"" + path + "\" (revision " + revision + ")");

            // FIXME: git-show doesn't decrypt objects, so we can't use it to retrieve
            // files from the index. This is a suboptimal workaround but it does the job
            if (this.is_encrypted)
            {
                // Restore the older file...
                SparkleGit git = new SparkleGit(LocalPath, "checkout " + revision + " \"" + path + "\"");
                git.StartAndWaitForExit();

                string local_file_path = Path.Combine(LocalPath, path);

                // ...move it...
                try {
                    File.Move(local_file_path, target_file_path);
                } catch {
                    SparkleLogger.LogInfo("Git",
                                          Name + " | Could not move \"" + local_file_path + "\" to \"" + target_file_path + "\"");
                }

                // ...and restore the most recent revision
                git = new SparkleGit(LocalPath, "checkout " + CurrentRevision + " \"" + path + "\"");
                git.StartAndWaitForExit();

                // The correct way
            }
            else
            {
                path = path.Replace("\"", "\\\"");

                SparkleGit git = new SparkleGit(LocalPath, "show " + revision + ":\"" + path + "\"");
                git.Start();

                FileStream stream = File.OpenWrite(target_file_path);
                git.StandardOutput.BaseStream.CopyTo(stream);
                stream.Close();

                git.WaitForExit();
            }

            if (target_file_path.StartsWith(LocalPath))
            {
                new Thread(() => OnFileActivity(null)).Start();
            }
        }
Ejemplo n.º 9
0
        public override void Complete()
        {
            if (!IsFetchedRepoEmpty)
            {
                SparkleGit git = new SparkleGit(TargetFolder, "checkout --quiet HEAD");
                git.StartAndWaitForExit();
            }

            base.Complete();
        }
Ejemplo n.º 10
0
        public void InstallGitBinConfiguration()
        {
            string [] settings = new string [] {
                "core.bigFileThreshold 1024g",
                "filter.bin.clean \"git bin clean %f\"",
                "filter.bin.smudge \"git bin smudge\""
            };

            foreach (string setting in settings)
            {
                SparkleGit git_config = new SparkleGit(TargetFolder, "config " + setting);
                git_config.StartAndWaitForExit();
            }
        }
Ejemplo n.º 11
0
        private void ConfigureGitBin()
        {
            SparkleGit git = new SparkleGit(LocalPath, "config filter.bin.clean \"git bin clean %f\"");

            git.StartAndWaitForExit();

            git = new SparkleGit(LocalPath, "config filter.bin.smudge \"git bin smudge\"");
            git.StartAndWaitForExit();

            git = new SparkleGit(LocalPath, "config git-bin.sftpUrl \"" + RemoteUrl + "\"");
            git.StartAndWaitForExit();

            git = new SparkleGit(LocalPath, "config git-bin.sftpPrivateKeyFile \"" + base.local_config.User.PrivateKeyFilePath + "\"");
            git.StartAndWaitForExit();
        }
Ejemplo n.º 12
0
        public SparkleRepo(string path, SparkleConfig config) : base(path, config)
        {
            SparkleGit git = new SparkleGit(LocalPath, "config --get filter.bin.clean");

            git.Start();
            git.WaitForExit();

            this.use_git_bin = (git.ExitCode == 0);

            string rebase_apply_path = new string [] { LocalPath, ".git", "rebase-apply" }.Combine();

            if (Directory.Exists(rebase_apply_path))
            {
                git = new SparkleGit(LocalPath, "rebase --abort");
                git.StartAndWaitForExit();
            }
        }
Ejemplo n.º 13
0
        public void InstallGitBinConfiguration()
        {
            string [] settings = new string [] {
                "core.bigFileThreshold 8g",
                "filter.bin.clean \"git bin clean %f\"",
                "filter.bin.smudge \"git bin smudge\"",
                "git-bin.chunkSize 1m",
                "git-bin.s3bucket \"your bucket name\"",
                "git-bin.s3key \"your key\"",
                "git-bin.s3secretKey \"your secret key\""
            };

            foreach (string setting in settings)
            {
                SparkleGit git_config = new SparkleGit(TargetFolder, "config " + setting);
                git_config.StartAndWaitForExit();
            }
        }
Ejemplo n.º 14
0
        // Commits the made changes
        private void Commit(string message)
        {
            SparkleGit git;

            if (!this.user_is_set)
            {
                git = new SparkleGit(LocalPath, "config user.name \"" + base.local_config.User.Name + "\"");
                git.StartAndWaitForExit();

                git = new SparkleGit(LocalPath, "config user.email \"" + base.local_config.User.Email + "\"");
                git.StartAndWaitForExit();

                this.user_is_set = true;
            }

            git = new SparkleGit(LocalPath, "commit --all --message=\"" + message + "\" " +
                                 "--author=\"" + base.local_config.User.Name + " <" + base.local_config.User.Email + ">\"");

            git.StartAndReadStandardOutput();
        }
Ejemplo n.º 15
0
        public SparkleRepo(string path, SparkleConfig config) : base(path, config)
        {
            // TODO: Set git locale to en-US

            SparkleGit git = new SparkleGit(LocalPath, "config --get filter.bin.clean");

            git.StartAndWaitForExit();

            this.use_git_bin = (git.ExitCode == 0);

            git = new SparkleGit(LocalPath, "config remote.origin.url \"" + RemoteUrl + "\"");
            git.StartAndWaitForExit();

            string rebase_apply_path = new string [] { LocalPath, ".git", "rebase-apply" }.Combine();

            if (Directory.Exists(rebase_apply_path))
            {
                git = new SparkleGit(LocalPath, "rebase --abort");
                git.StartAndWaitForExit();
            }
        }
Ejemplo n.º 16
0
        // Merges the fetched changes
        private bool Merge()
        {
            string message = FormatCommitMessage();

            if (message != null)
            {
                Add();
                Commit(message);
            }

            SparkleGit git;

            // Stop if we're already in a merge because something went wrong
            if (this.in_merge)
            {
                git = new SparkleGit(LocalPath, "merge --abort");
                git.StartAndWaitForExit();

                return(false);
            }

            // Temporarily change the ignorecase setting to true to avoid
            // conflicts in file names due to letter case changes
            git = new SparkleGit(LocalPath, "config core.ignorecase true");
            git.StartAndWaitForExit();

            git = new SparkleGit(LocalPath, "merge FETCH_HEAD");
            git.StartInfo.RedirectStandardOutput = false;

            string error_output = git.StartAndReadStandardError();

            if (git.ExitCode != 0)
            {
                // Stop when we can't merge due to locked local files
                // error: cannot stat 'filename': Permission denied
                if (error_output.Contains("error: cannot stat"))
                {
                    Error = ErrorStatus.UnreadableFiles;
                    SparkleLogger.LogInfo("Git", Name + " | Error status changed to " + Error);

                    git = new SparkleGit(LocalPath, "merge --abort");
                    git.StartAndWaitForExit();

                    git = new SparkleGit(LocalPath, "config core.ignorecase false");
                    git.StartAndWaitForExit();

                    return(false);
                }
                else
                {
                    SparkleLogger.LogInfo("Git", error_output);
                    SparkleLogger.LogInfo("Git", Name + " | Conflict detected, trying to get out...");

                    while (this.in_merge && HasLocalChanges)
                    {
                        try {
                            ResolveConflict();
                        } catch (Exception e) {
                            SparkleLogger.LogInfo("Git", Name + " | Failed to resolve conflict, trying again...", e);
                        }
                    }

                    SparkleLogger.LogInfo("Git", Name + " | Conflict resolved");
                }
            }

            git = new SparkleGit(LocalPath, "config core.ignorecase false");
            git.StartAndWaitForExit();

            return(true);
        }
Ejemplo n.º 17
0
        private void ResolveConflict()
        {
            // This is a list of conflict status codes that Git uses, their
            // meaning, and how SparkleShare should handle them.
            //
            // DD    unmerged, both deleted    -> Do nothing
            // AU    unmerged, added by us     -> Use server's, save ours as a timestamped copy
            // UD    unmerged, deleted by them -> Use ours
            // UA    unmerged, added by them   -> Use server's, save ours as a timestamped copy
            // DU    unmerged, deleted by us   -> Use server's
            // AA    unmerged, both added      -> Use server's, save ours as a timestamped copy
            // UU    unmerged, both modified   -> Use server's, save ours as a timestamped copy
            // ??    unmerged, new files       -> Stage the new files
            //
            // Note that a rebase merge works by replaying each commit from the working branch on
            // top of the upstream branch. Because of this, when a merge conflict happens the
            // side reported as 'ours' is the so-far rebased series, starting with upstream,
            // and 'theirs' is the working branch. In other words, the sides are swapped.
            //
            // So: 'ours' means the 'server's version' and 'theirs' means the 'local version' after this comment

            SparkleGit git_status = new SparkleGit(LocalPath, "status --porcelain");
            string     output     = git_status.StartAndReadStandardOutput();

            string [] lines         = output.Split("\n".ToCharArray());
            bool      changes_added = false;

            foreach (string line in lines)
            {
                string conflicting_path = line.Substring(3);
                conflicting_path = EnsureSpecialCharacters(conflicting_path);
                conflicting_path = conflicting_path.Replace("\"", "\\\"");

                SparkleLogger.LogInfo("Git", Name + " | Conflict type: " + line);

                // Ignore conflicts in the .sparkleshare file and use the local version
                if (conflicting_path.EndsWith(".sparkleshare") || conflicting_path.EndsWith(".empty"))
                {
                    // Recover local version
                    SparkleGit git_theirs = new SparkleGit(LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
                    git_theirs.StartAndWaitForExit();

                    File.SetAttributes(Path.Combine(LocalPath, conflicting_path), FileAttributes.Hidden);
                    changes_added = true;

                    continue;
                }

                // Both the local and server version have been modified
                if (line.StartsWith("UU") || line.StartsWith("AA") ||
                    line.StartsWith("AU") || line.StartsWith("UA"))
                {
                    // Recover local version
                    SparkleGit git_theirs = new SparkleGit(LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
                    git_theirs.StartAndWaitForExit();

                    // Append a timestamp to local version.
                    // Windows doesn't allow colons in the file name, so
                    // we use "h" between the hours and minutes instead.
                    string timestamp  = DateTime.Now.ToString("MMM d H\\hmm");
                    string their_path = Path.GetFileNameWithoutExtension(conflicting_path) +
                                        " (" + base.local_config.User.Name + ", " + timestamp + ")" + Path.GetExtension(conflicting_path);

                    string abs_conflicting_path = Path.Combine(LocalPath, conflicting_path);
                    string abs_their_path       = Path.Combine(LocalPath, their_path);

                    File.Move(abs_conflicting_path, abs_their_path);

                    // Recover server version
                    SparkleGit git_ours = new SparkleGit(LocalPath, "checkout --ours \"" + conflicting_path + "\"");
                    git_ours.StartAndWaitForExit();

                    changes_added = true;

                    // The local version has been modified, but the server version was removed
                }
                else if (line.StartsWith("DU"))
                {
                    // The modified local version is already in the checkout, so it just needs to be added.
                    // We need to specifically mention the file, so we can't reuse the Add () method
                    SparkleGit git_add = new SparkleGit(LocalPath, "add \"" + conflicting_path + "\"");
                    git_add.StartAndWaitForExit();

                    changes_added = true;
                }
            }

            Add();
            SparkleGit git;

            if (changes_added)
            {
                git = new SparkleGit(LocalPath, "rebase --continue");
            }
            else
            {
                git = new SparkleGit(LocalPath, "rebase --skip");
            }

            git.StartInfo.RedirectStandardOutput = false;
            git.StartAndWaitForExit();
        }
Ejemplo n.º 18
0
        // Merges the fetched changes
        private bool Rebase()
        {
            if (HasLocalChanges)
            {
                Add();

                string commit_message = FormatCommitMessage();
                Commit(commit_message);
            }

            // Temporarily change the ignorecase setting to true to avoid
            // conflicts in file names due to case changes
            SparkleGit git = new SparkleGit(LocalPath, "config core.ignorecase true");

            git.StartAndWaitForExit();

            git = new SparkleGit(LocalPath, "rebase FETCH_HEAD");
            git.StartInfo.RedirectStandardOutput = false;

            string error_output = git.StartAndReadStandardError();

            if (git.ExitCode != 0)
            {
                // Stop when we can't rebase due to locked local files
                // error: cannot stat 'filename': Permission denied
                if (error_output.Contains("error: cannot stat"))
                {
                    Error = ErrorStatus.LockedFiles;
                    SparkleLogger.LogInfo("Git", Name + " | Error status changed to " + Error);

                    git = new SparkleGit(LocalPath, "rebase --abort");
                    git.StartAndWaitForExit();

                    git = new SparkleGit(LocalPath, "config core.ignorecase false");
                    git.StartAndWaitForExit();

                    return(false);
                }
                else
                {
                    SparkleLogger.LogInfo("Git", Name + " | Conflict detected, trying to get out...");
                    string rebase_apply_path = new string [] { LocalPath, ".git", "rebase-apply" }.Combine();

                    while (Directory.Exists(rebase_apply_path) && HasLocalChanges)
                    {
                        try {
                            ResolveConflict();
                        } catch (IOException e) {
                            SparkleLogger.LogInfo("Git", Name + " | Failed to resolve conflict, trying again... (" + e.Message + ")");
                        }
                    }

                    SparkleLogger.LogInfo("Git", Name + " | Conflict resolved");
                    OnConflictResolved();
                }
            }

            git = new SparkleGit(LocalPath, "config core.ignorecase false");
            git.StartAndWaitForExit();

            return(true);
        }
Ejemplo n.º 19
0
        // Stages the made changes
        private void Add()
        {
            SparkleGit git = new SparkleGit(LocalPath, "add --all");

            git.StartAndWaitForExit();
        }
Ejemplo n.º 20
0
        public override bool SyncUp()
        {
            if (HasLocalChanges)
            {
                Add();

                string message = FormatCommitMessage();
                Commit(message);
            }

            SparkleGit git;

            if (this.use_git_bin)
            {
                SparkleGitBin git_bin = new SparkleGitBin(LocalPath, "push");
                git_bin.StartAndWaitForExit();

                // TODO: Progress
            }

            git = new SparkleGit(LocalPath, "push --progress \"" + RemoteUrl + "\" " + this.branch);

            git.StartInfo.RedirectStandardError = true;
            git.Start();

            double percentage = 1.0;

            while (!git.StandardError.EndOfStream)
            {
                string line   = git.StandardError.ReadLine();
                Match  match  = this.progress_regex.Match(line);
                double speed  = 0.0;
                double number = 0.0;

                if (match.Success)
                {
                    number = double.Parse(match.Groups [1].Value);

                    // The pushing progress consists of two stages: the "Compressing
                    // objects" stage which we count as 20% of the total progress, and
                    // the "Writing objects" stage which we count as the last 80%
                    if (line.StartsWith("Compressing"))
                    {
                        // "Compressing objects" stage
                        number = (number / 100 * 20);
                    }
                    else
                    {
                        // "Writing objects" stage
                        number = (number / 100 * 80 + 20);
                        Match speed_match = this.speed_regex.Match(line);

                        if (speed_match.Success)
                        {
                            speed = double.Parse(speed_match.Groups [1].Value) * 1024;

                            if (speed_match.Groups [2].Value.Equals("M"))
                            {
                                speed = speed * 1024;
                            }
                        }
                    }
                }
                else
                {
                    SparkleLogger.LogInfo("Git", Name + " | " + line);

                    if (FindError(line))
                    {
                        return(false);
                    }
                }

                if (number >= percentage)
                {
                    percentage = number;
                    base.OnProgressChanged(percentage, speed);
                }
            }

            git.WaitForExit();
            UpdateSizes();

            if (git.ExitCode == 0)
            {
                ClearCache();

                string salt_file_path = new string [] { LocalPath, ".git", "salt" }.Combine();

                // If the repo is encrypted, create a branch to
                // store the salt in and push it to the host
                if (File.Exists(salt_file_path))
                {
                    string salt = File.ReadAllText(salt_file_path).Trim();

                    SparkleGit git_salt = new SparkleGit(LocalPath, "branch salt-" + salt);
                    git_salt.StartAndWaitForExit();

                    git_salt = new SparkleGit(LocalPath, "push origin salt-" + salt);
                    git_salt.StartAndWaitForExit();

                    File.Delete(salt_file_path);
                }

                return(true);
            }
            else
            {
                Error = ErrorStatus.HostUnreachable;
                return(false);
            }
        }
Ejemplo n.º 21
0
        public override bool SyncUp()
        {
            if (HasLocalChanges)
            {
                Add();

                string message = FormatCommitMessage();
                Commit(message);
            }

            SparkleGit git;

            if (this.use_git_bin)
            {
                if (this.remote_url_is_set)
                {
                    git = new SparkleGit(LocalPath, "config remote.origin.url \"" + RemoteUrl + "\"");
                    git.StartAndWaitForExit();

                    this.remote_url_is_set = true;
                }

                SparkleGitBin git_bin = new SparkleGitBin(LocalPath, "push");
                git_bin.StartAndWaitForExit();

                // TODO: Progress
            }

            git = new SparkleGit(LocalPath,
                                 "push --progress " + // Redirects progress stats to standarderror
                                 "\"" + RemoteUrl + "\" master");

            git.StartInfo.RedirectStandardError = true;
            git.Start();

            double percentage     = 1.0;
            Regex  progress_regex = new Regex(@"([0-9]+)%", RegexOptions.Compiled);

            while (!git.StandardError.EndOfStream)
            {
                string line   = git.StandardError.ReadLine();
                Match  match  = progress_regex.Match(line);
                string speed  = "";
                double number = 0.0;

                if (match.Success)
                {
                    number = double.Parse(match.Groups [1].Value);

                    // The pushing progress consists of two stages: the "Compressing
                    // objects" stage which we count as 20% of the total progress, and
                    // the "Writing objects" stage which we count as the last 80%
                    if (line.StartsWith("Compressing"))
                    {
                        // "Compressing objects" stage
                        number = (number / 100 * 20);
                    }
                    else
                    {
                        if (line.StartsWith("ERROR: QUOTA EXCEEDED"))
                        {
                            int quota_limit = int.Parse(line.Substring(21).Trim());
                            throw new QuotaExceededException("Quota exceeded", quota_limit);
                        }

                        // "Writing objects" stage
                        number = (number / 100 * 80 + 20);

                        if (line.Contains("|"))
                        {
                            speed = line.Substring(line.IndexOf("|") + 1).Trim();
                            speed = speed.Replace(", done.", "").Trim();
                            speed = speed.Replace("i", "");
                            speed = speed.Replace("KB/s", "ᴋʙ/s");
                            speed = speed.Replace("MB/s", "ᴍʙ/s");
                        }
                    }
                }
                else
                {
                    SparkleLogger.LogInfo("Git", Name + " | " + line);
                }

                if (number >= percentage)
                {
                    percentage = number;
                    base.OnProgressChanged(percentage, speed);
                }
            }

            git.WaitForExit();
            UpdateSizes();

            if (git.ExitCode == 0)
            {
                ClearCache();
                return(true);
            }
            else
            {
                return(false);
            }
        }
Ejemplo n.º 22
0
        private void ResolveConflict()
        {
            // This is a list of conflict status codes that Git uses, their
            // meaning, and how SparkleShare should handle them.
            //
            // DD    unmerged, both deleted    -> Do nothing
            // AU    unmerged, added by us     -> Use server's, save ours as a timestamped copy
            // UD    unmerged, deleted by them -> Use ours
            // UA    unmerged, added by them   -> Use server's, save ours as a timestamped copy
            // DU    unmerged, deleted by us   -> Use server's
            // AA    unmerged, both added      -> Use server's, save ours as a timestamped copy
            // UU    unmerged, both modified   -> Use server's, save ours as a timestamped copy
            // ??    unmerged, new files       -> Stage the new files

            SparkleGit git_status = new SparkleGit(LocalPath, "status --porcelain");
            string     output     = git_status.StartAndReadStandardOutput();

            string [] lines = output.Split("\n".ToCharArray());
            bool      trigger_conflict_event = false;

            foreach (string line in lines)
            {
                string conflicting_path = line.Substring(3);
                conflicting_path = EnsureSpecialCharacters(conflicting_path);
                conflicting_path = conflicting_path.Trim("\"".ToCharArray());

                // Remove possible rename indicators
                string [] separators = { " -> \"", " -> " };
                foreach (string separator in separators)
                {
                    if (conflicting_path.Contains(separator))
                    {
                        conflicting_path = conflicting_path.Substring(
                            conflicting_path.IndexOf(separator) + separator.Length);
                    }
                }

                SparkleLogger.LogInfo("Git", Name + " | Conflict type: " + line);

                // Ignore conflicts in hidden files and use the local versions
                if (conflicting_path.EndsWith(".sparkleshare") || conflicting_path.EndsWith(".empty"))
                {
                    SparkleLogger.LogInfo("Git", Name + " | Ignoring conflict in special file: " + conflicting_path);

                    // Recover local version
                    SparkleGit git_ours = new SparkleGit(LocalPath, "checkout --ours \"" + conflicting_path + "\"");
                    git_ours.StartAndWaitForExit();

                    string abs_conflicting_path = Path.Combine(LocalPath, conflicting_path);

                    if (File.Exists(abs_conflicting_path))
                    {
                        File.SetAttributes(abs_conflicting_path, FileAttributes.Hidden);
                    }

                    continue;
                }

                SparkleLogger.LogInfo("Git", Name + " | Resolving: " + conflicting_path);

                // Both the local and server version have been modified
                if (line.StartsWith("UU") || line.StartsWith("AA") ||
                    line.StartsWith("AU") || line.StartsWith("UA"))
                {
                    // Recover local version
                    SparkleGit git_ours = new SparkleGit(LocalPath, "checkout --ours \"" + conflicting_path + "\"");
                    git_ours.StartAndWaitForExit();

                    // Append a timestamp to local version.
                    // Windows doesn't allow colons in the file name, so
                    // we use "h" between the hours and minutes instead.
                    string timestamp = DateTime.Now.ToString("MMM d H\\hmm");
                    string our_path  = Path.GetFileNameWithoutExtension(conflicting_path) +
                                       " (" + base.local_config.User.Name + ", " + timestamp + ")" + Path.GetExtension(conflicting_path);

                    string abs_conflicting_path = Path.Combine(LocalPath, conflicting_path);
                    string abs_our_path         = Path.Combine(LocalPath, our_path);

                    if (File.Exists(abs_conflicting_path) && !File.Exists(abs_our_path))
                    {
                        File.Move(abs_conflicting_path, abs_our_path);
                    }

                    // Recover server version
                    SparkleGit git_theirs = new SparkleGit(LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
                    git_theirs.StartAndWaitForExit();

                    trigger_conflict_event = true;


                    // The server version has been modified, but the local version was removed
                }
                else if (line.StartsWith("DU"))
                {
                    // The modified local version is already in the checkout, so it just needs to be added.
                    // We need to specifically mention the file, so we can't reuse the Add () method
                    SparkleGit git_add = new SparkleGit(LocalPath, "add \"" + conflicting_path + "\"");
                    git_add.StartAndWaitForExit();


                    // The local version has been modified, but the server version was removed
                }
                else if (line.StartsWith("UD"))
                {
                    // Recover server version
                    SparkleGit git_theirs = new SparkleGit(LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
                    git_theirs.StartAndWaitForExit();


                    // Server and local versions were removed
                }
                else if (line.StartsWith("DD"))
                {
                    SparkleLogger.LogInfo("Git", Name + " | No need to resolve: " + line);

                    // New local files
                }
                else if (line.StartsWith("??"))
                {
                    SparkleLogger.LogInfo("Git", Name + " | Found new file, no need to resolve: " + line);
                }
                else
                {
                    SparkleLogger.LogInfo("Git", Name + " | Don't know what to do with: " + line);
                }
            }

            Add();

            SparkleGit git = new SparkleGit(LocalPath, "commit --message \"Conflict resolution by SparkleShare\"");

            git.StartInfo.RedirectStandardOutput = false;
            git.StartAndWaitForExit();

            if (trigger_conflict_event)
            {
                OnConflictResolved();
            }
        }