Example #1
0
        public GitRepository(string path, Configuration config, SSHAuthenticationInfo auth_info)
            : base(path, config)
        {
            this.auth_info = auth_info;

            var git_config = new GitCommand (LocalPath, "config core.ignorecase false");
            git_config.StartAndWaitForExit ();

            git_config = new GitCommand (LocalPath, "config remote.origin.url \"" + RemoteUrl + "\"");
            git_config.StartAndWaitForExit ();
        }
Example #2
0
        StorageType?DetermineStorageType()
        {
            var git_ls_remote = new GitCommand(Configuration.DefaultConfiguration.TmpPath,
                                               string.Format("ls-remote --heads \"{0}\"", RemoteUrl), auth_info);

            string output = git_ls_remote.StartAndReadStandardOutput();

            if (git_ls_remote.ExitCode != 0)
            {
                return(null);
            }

            if (string.IsNullOrWhiteSpace(output))
            {
                return(StorageType.Unknown);
            }

            foreach (string line in output.Split("\n".ToCharArray()))
            {
                string [] line_parts = line.Split('/');
                string    branch     = line_parts [line_parts.Length - 1];

                if (branch == "x-sparkleshare-lfs")
                {
                    return(StorageType.LargeFiles);
                }

                string encrypted_storage_prefix = "x-sparkleshare-encrypted-";

                if (branch.StartsWith(encrypted_storage_prefix))
                {
                    password_salt = branch.Replace(encrypted_storage_prefix, "");
                    return(StorageType.Encrypted);
                }
            }

            return(StorageType.Plain);
        }
Example #3
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");
            }

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

            // Restore the older file...
            var git = new GitCommand(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 (Exception e) {
                string message = string.Format("Failed to move \"{0}\" to \"{1}\"", local_file_path, target_file_path);
                Logger.LogInfo("Git", Name + " | " + message, e);
            }

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

            if (target_file_path.StartsWith(LocalPath))
            {
                new Thread(() => OnFileActivity(null)).Start();
            }
        }
Example #4
0
        void InstallGitLFS()
        {
            var git_config_required = new GitCommand(TargetFolder, "config filter.lfs.required true");

            string GIT_SSH_COMMAND = GitCommand.FormatGitSSHCommand(auth_info);
            string smudge_command;
            string clean_command;

            if (InstallationInfo.OperatingSystem == OS.macOS)
            {
                smudge_command = "env GIT_SSH_COMMAND='" + GIT_SSH_COMMAND + "' " +
                                 Path.Combine(Configuration.DefaultConfiguration.BinPath, "git-lfs").Replace("\\", "/") + " smudge %f";

                clean_command = Path.Combine(Configuration.DefaultConfiguration.BinPath, "git-lfs").Replace("\\", "/") + " clean %f";
            }
            else if (InstallationInfo.OperatingSystem == OS.Windows)
            {
                smudge_command = "env GIT_SSH_COMMAND='" + GIT_SSH_COMMAND + "' " +
                                 Path.Combine(Path.GetDirectoryName(GitCommand.GitPath), "git-lfs").Replace("\\", "/") + " smudge %f";

                clean_command = Path.Combine(Path.GetDirectoryName(GitCommand.GitPath), "git-lfs").Replace("\\", "/") + " clean %f";
            }
            else
            {
                smudge_command = "env GIT_SSH_COMMAND='" + GIT_SSH_COMMAND + "' git-lfs smudge %f";
                clean_command  = "git-lfs clean %f";
            }

            var git_config_smudge = new GitCommand(TargetFolder,
                                                   string.Format("config filter.lfs.smudge \"{0}\"", smudge_command));

            var git_config_clean = new GitCommand(TargetFolder,
                                                  string.Format("config filter.lfs.clean '{0}'", clean_command));

            git_config_required.StartAndWaitForExit();
            git_config_clean.StartAndWaitForExit();
            git_config_smudge.StartAndWaitForExit();
        }
Example #5
0
        public GitRepository(string path, Configuration config, SSHAuthenticationInfo auth_info) : base(path, config)
        {
            this.auth_info = auth_info;

            var git_config = new GitCommand(LocalPath, "config core.ignorecase false");

            git_config.StartAndWaitForExit();

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

            git_config = new GitCommand(LocalPath, "config core.sshCommand " + GitCommand.FormatGitSSHCommand(auth_info));
            git_config.StartAndWaitForExit();

            if (InstallationInfo.OperatingSystem != OS.Windows)
            {
                string pre_push_hook_path = Path.Combine(LocalPath, ".git", "hooks", "pre-push");

                // TODO: Use proper API
                var chmod = new Command("chmod", "700 " + pre_push_hook_path);
                chmod.StartAndWaitForExit();
            }
        }
Example #6
0
        // The pre-push hook may have been changed by Git LFS, overwrite it to use our own configuration
        void PrepareGitLFS()
        {
            string pre_push_hook_path = Path.Combine(LocalPath, ".git", "hooks", "pre-push");
            string pre_push_hook_content;

            if (InstallationInfo.OperatingSystem == OS.macOS)
            {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand(auth_info) + "' " +
                    Path.Combine(Configuration.DefaultConfiguration.BinPath, "git-lfs").Replace("\\", "/") + " pre-push \"$@\"";
            }
            else if (InstallationInfo.OperatingSystem == OS.Windows)
            {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand(auth_info) + "' " +
                    Path.Combine(Path.GetDirectoryName(GitCommand.GitPath), "git-lfs").Replace("\\", "/") + " pre-push \"$@\"";
            }
            else
            {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand(auth_info) + "' " +
                    "git-lfs pre-push \"$@\"";
            }

            if (InstallationInfo.OperatingSystem != OS.Windows)
            {
                // TODO: Use proper API
                var chmod = new Command("chmod", "700 " + pre_push_hook_path);
                chmod.StartAndWaitForExit();
            }

            Directory.CreateDirectory(Path.GetDirectoryName(pre_push_hook_path));
            File.WriteAllText(pre_push_hook_path, pre_push_hook_content);
        }
Example #7
0
        public override void EnableFetchedRepoCrypto(string password)
        {
            string password_file       = ".git/info/encryption_password";
            var    git_config_required = new GitCommand(TargetFolder, "config filter.encryption.required true");

            var git_config_smudge = new GitCommand(TargetFolder, "config filter.encryption.smudge " +
                                                   string.Format("\"openssl enc -d -aes-256-cbc -base64 -S {0} -pass file:{1} -md sha256\"", password_salt, password_file));

            var git_config_clean = new GitCommand(TargetFolder, "config filter.encryption.clean " +
                                                  string.Format("\"openssl enc -e -aes-256-cbc -base64 -S {0} -pass file:{1} -md sha256\"", password_salt, password_file));

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

            string git_info_path = Path.Combine(TargetFolder, ".git", "info");

            Directory.CreateDirectory(git_info_path);

            // Store the password, TODO: 600 permissions
            string password_file_path = Path.Combine(git_info_path, "encryption_password");

            File.WriteAllText(password_file_path, password.SHA256(password_salt));
        }
        public override bool IsFetchedRepoPasswordCorrect(string password)
        {
            string password_check_file_path = Path.Combine (TargetFolder, ".sparkleshare");

            if (!File.Exists (password_check_file_path)) {
                var git_show = new GitCommand (TargetFolder, "show HEAD:.sparkleshare");
                string output = git_show.StartAndReadStandardOutput ();

                if (git_show.ExitCode == 0)
                    File.WriteAllText (password_check_file_path, output);
                else
                    return false;
            }

            string args = string.Format ("enc -d -aes-256-cbc -base64 -S {0} -pass pass:{1} -in \"{2}\"",
                password_salt, password.SHA256 (password_salt), password_check_file_path);

            var process = new Command ("openssl", args);

            process.StartInfo.WorkingDirectory = TargetFolder;
            process.StartAndWaitForExit ();

            if (process.ExitCode == 0) {
                File.Delete (password_check_file_path);
                return true;
            }

            return false;
        }
        StorageType? DetermineStorageType()
        {
            var git_ls_remote = new GitCommand (Configuration.DefaultConfiguration.TmpPath,
                string.Format ("ls-remote --heads \"{0}\"", RemoteUrl), auth_info);

            string output = git_ls_remote.StartAndReadStandardOutput ();

            if (git_ls_remote.ExitCode != 0)
                return null;

            if (string.IsNullOrWhiteSpace (output))
                return StorageType.Unknown;

            foreach (string line in output.Split ("\n".ToCharArray ())) {
                string [] line_parts = line.Split ('/');
                string branch = line_parts [line_parts.Length - 1];

                if (branch == "x-sparkleshare-lfs")
                    return StorageType.LargeFiles;

                string encrypted_storage_prefix = "x-sparkleshare-encrypted-";

                if (branch.StartsWith (encrypted_storage_prefix)) {
                    password_salt = branch.Replace (encrypted_storage_prefix, "");
                    return StorageType.Encrypted;
                }
            }

            return StorageType.Plain;
        }
        public override void EnableFetchedRepoCrypto(string password)
        {
            string password_file = ".git/info/encryption_password";
            var git_config_required = new GitCommand (TargetFolder, "config filter.encryption.required true");

            var git_config_smudge = new GitCommand (TargetFolder, "config filter.encryption.smudge " +
                string.Format ("\"openssl enc -d -aes-256-cbc -base64 -S {0} -pass file:{1}\"", password_salt, password_file));

            var git_config_clean = new GitCommand (TargetFolder, "config filter.encryption.clean " +
                string.Format ("\"openssl enc -e -aes-256-cbc -base64 -S {0} -pass file:{1}\"", password_salt, password_file));

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

            // Store the password, TODO: 600 permissions
            string password_file_path = Path.Combine (TargetFolder, ".git", "info", "encryption_password");
            File.WriteAllText (password_file_path, password.SHA256 (password_salt));
        }
        public override bool Fetch()
        {
            if (!base.Fetch ())
                return false;

            StorageType? storage_type = DetermineStorageType ();

            if (storage_type == null)
                return false;

            FetchedRepoStorageType = (StorageType) storage_type;

            string git_clone_command = "clone --progress --no-checkout";

            if (!FetchPriorHistory)
                git_clone_command += " --depth=1";

            if (storage_type == StorageType.LargeFiles)
                git_clone_command = "lfs clone --progress --no-checkout";

            git_clone = new GitCommand (Configuration.DefaultConfiguration.TmpPath,
                string.Format ("{0} \"{1}\" \"{2}\"", git_clone_command, RemoteUrl, TargetFolder),
                auth_info);

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

            StreamReader output_stream = git_clone.StandardError;

            if (FetchedRepoStorageType == StorageType.LargeFiles)
                output_stream = git_clone.StandardOutput;

            double percentage = 0;
            double speed = 0;
            string information = "";

            while (!output_stream.EndOfStream) {
                string line = output_stream.ReadLine ();

                ErrorStatus error = GitCommand.ParseProgress (line, out percentage, out speed, out information);

                if (error != ErrorStatus.None) {
                    IsActive = false;
                    git_clone.Kill ();
                    git_clone.Dispose ();

                    return false;
                }

                OnProgressChanged (percentage, speed, information);
            }

            git_clone.WaitForExit ();

            if (git_clone.ExitCode != 0)
                return false;

            Thread.Sleep (500);
            OnProgressChanged (100, 0, "");
            Thread.Sleep (500);

            return true;
        }
Example #12
0
        // Commits the made changes
        void Commit(string message)
        {
            GitCommand git;

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

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

                this.user_is_set = true;
            }

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

            git.StartAndReadStandardOutput ();
        }
Example #13
0
        public override bool SyncDown()
        {
            string lfs_is_behind_file_path = Path.Combine (LocalPath, ".git", "lfs", "is_behind");

            if (StorageType == StorageType.LargeFiles)
                File.Create (lfs_is_behind_file_path);

            var git_fetch = new GitCommand (LocalPath, "fetch --progress origin " + branch, auth_info);

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

            if (!ReadStream (git_fetch))
                return false;

            git_fetch.WaitForExit ();

            if (git_fetch.ExitCode != 0) {
                Error = ErrorStatus.HostUnreachable;
                return false;
            }

            if (Merge ()) {
                if (StorageType == StorageType.LargeFiles) {
                    // Pull LFS files manually to benefit from concurrency
                    var git_lfs_pull = new GitCommand (LocalPath, "lfs pull origin", auth_info);
                    git_lfs_pull.StartAndWaitForExit ();

                    if (git_lfs_pull.ExitCode != 0) {
                        Error = ErrorStatus.HostUnreachable;
                        return false;
                    }

                    if (File.Exists (lfs_is_behind_file_path))
                        File.Delete (lfs_is_behind_file_path);
                }

                UpdateSizes ();
                return true;
            }

            return false;
        }
Example #14
0
        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

            var git_status = new GitCommand (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);
                    }
                }

                Logger.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")) {
                    Logger.LogInfo ("Git", Name + " | Ignoring conflict in special file: " + conflicting_path);

                    // Recover local version
                    var git_ours = new GitCommand (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;
                }

                Logger.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
                    var git_ours = new GitCommand (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
                    var git_theirs = new GitCommand (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
                    var git_add = new GitCommand (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
                    var git_theirs = new GitCommand (LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
                    git_theirs.StartAndWaitForExit ();

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

                // New local files
                } else if (line.StartsWith ("??")) {
                    Logger.LogInfo ("Git", Name + " | Found new file, no need to resolve: " + line);

                } else {
                    Logger.LogInfo ("Git", Name + " | Don't know what to do with: " + line);
                }
            }

            Add ();

            var git = new GitCommand (LocalPath, "commit --message \"Conflict resolution by SparkleShare\"");
            git.StartInfo.RedirectStandardOutput = false;
            git.StartAndWaitForExit ();

            if (trigger_conflict_event)
                OnConflictResolved ();
        }
        public override string Complete(StorageType selected_storage_type)
        {
            string identifier = base.Complete (selected_storage_type);
            string identifier_path = Path.Combine (TargetFolder, ".sparkleshare");

            InstallConfiguration ();
            InstallGitLFS ();

            InstallAttributeRules ();
            InstallExcludeRules ();

            if (IsFetchedRepoEmpty) {
                File.WriteAllText (identifier_path, identifier);

                var git_add    = new GitCommand (TargetFolder, "add .sparkleshare");
                var git_commit = new GitCommand (TargetFolder, "commit --message=\"Initial commit by SparkleShare\"");

                // We can't do the "commit --all" shortcut because it doesn't add untracked files
                git_add.StartAndWaitForExit ();
                git_commit.StartAndWaitForExit ();

                // These branches will be pushed  later by "git push --all"
                if (selected_storage_type == StorageType.LargeFiles) {
                    var git_branch = new GitCommand (TargetFolder, "branch x-sparkleshare-lfs", auth_info);
                    git_branch.StartAndWaitForExit ();
                }

                if (selected_storage_type == StorageType.Encrypted) {
                    var git_branch = new GitCommand (TargetFolder,
                        string.Format ("branch x-sparkleshare-encrypted-{0}", password_salt), auth_info);

                    git_branch.StartAndWaitForExit ();
                }

            } else {
                if (File.Exists (identifier_path))
                    identifier = File.ReadAllText (identifier_path).Trim ();

                string branch = "HEAD";
                string prefered_branch = "SparkleShare";

                // Prefer the "SparkleShare" branch if it exists
                var git_show_ref = new GitCommand (TargetFolder,
                   "show-ref --verify --quiet refs/heads/" + prefered_branch);

                git_show_ref.StartAndWaitForExit ();

                if (git_show_ref.ExitCode == 0)
                    branch = prefered_branch;

                var git_checkout = new GitCommand (TargetFolder, string.Format ("checkout --quiet --force {0}", branch));
                git_checkout.StartAndWaitForExit ();
            }

            // git-lfs may leave junk behind
            string git_lfs_tmp_path = Path.Combine (Configuration.DefaultConfiguration.TmpPath, "lfs");

            if (Directory.Exists (git_lfs_tmp_path))
                Directory.Delete (git_lfs_tmp_path, true);

            File.SetAttributes (identifier_path, FileAttributes.Hidden);
            return identifier;
        }
Example #16
0
        List<Change> ParseStatus()
        {
            List<Change> changes = new List<Change> ();

            var git_status = new GitCommand (LocalPath, "status --porcelain");
            git_status.Start ();

            while (!git_status.StandardOutput.EndOfStream) {
                string line = git_status.StandardOutput.ReadLine ();
                line        = line.Trim ();

                if (line.EndsWith (".empty") || line.EndsWith (".empty\""))
                    line = line.Replace (".empty", "");

                Change change;

                if (line.StartsWith ("R")) {
                    string path = line.Substring (3, line.IndexOf (" -> ") - 3).Trim ("\" ".ToCharArray ());
                    string moved_to_path = line.Substring (line.IndexOf (" -> ") + 4).Trim ("\" ".ToCharArray ());

                    change = new Change () {
                        Type = ChangeType.Moved,
                        Path = EnsureSpecialCharacters (path),
                        MovedToPath = EnsureSpecialCharacters (moved_to_path)
                    };

                } else {
                    string path = line.Substring (2).Trim ("\" ".ToCharArray ());
                    change = new Change () { Path = EnsureSpecialCharacters (path) };
                    change.Type = ChangeType.Added;

                    if (line.StartsWith ("M")) {
                        change.Type = ChangeType.Edited;

                    } else if (line.StartsWith ("D")) {
                        change.Type = ChangeType.Deleted;
                    }
                }

                changes.Add (change);
            }

            git_status.StandardOutput.ReadToEnd ();
            git_status.WaitForExit ();

            return changes;
        }
Example #17
0
        bool ReadStream(GitCommand command)
        {
            StreamReader output_stream = command.StandardError;

            if (StorageType == StorageType.LargeFiles)
                output_stream = command.StandardOutput;

            double percentage = 0;
            double speed = 0;
            string information = "";

            while (!output_stream.EndOfStream) {
                string line = output_stream.ReadLine ();
                ErrorStatus error = GitCommand.ParseProgress (line, out percentage, out speed, out information);

                if (error != ErrorStatus.None) {
                    Error = error;
                    information = line;

                    command.Kill ();
                    command.Dispose ();
                    Logger.LogInfo ("Git", Name + " | Error status changed to " + Error);

                    return false;
                }

                OnProgressChanged (percentage, speed, information);
            }

            return true;
        }
Example #18
0
        // Merges the fetched changes
        bool Merge()
        {
            string message = FormatCommitMessage ();

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

            GitCommand git;

            // Stop if we're already in a merge because something went wrong
            if (this.in_merge) {
                 git = new GitCommand (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 GitCommand (LocalPath, "config core.ignorecase true");
            git.StartAndWaitForExit ();

            git = new GitCommand (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;
                    Logger.LogInfo ("Git", Name + " | Error status changed to " + Error);

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

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

                    return false;

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

                    while (this.in_merge && HasLocalChanges) {
                        try {
                            ResolveConflict ();

                        } catch (Exception e) {
                            Logger.LogInfo ("Git", Name + " | Failed to resolve conflict, trying again...", e);
                        }
                    }

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

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

            return true;
        }
Example #19
0
        public override bool SyncUp()
        {
            if (!Add ()) {
                Error = ErrorStatus.UnreadableFiles;
                return false;
            }

            string message = base.status_message.Replace ("\"", "\\\"");

            if (string.IsNullOrEmpty (message))
                message = FormatCommitMessage ();

            if (message != null)
                Commit (message);

            string pre_push_hook_path = Path.Combine (LocalPath, ".git", "hooks", "pre-push");
            string pre_push_hook_content;

            // The pre-push hook may have been changed by Git LFS, overwrite it to use our own configuration
            if (InstallationInfo.OperatingSystem == OS.Mac) {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand (auth_info) + "' " +
                    Path.Combine (Configuration.DefaultConfiguration.BinPath, "git-lfs") + " pre-push \"$@\"";

            } else {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand (auth_info) + "' " +
                    "git-lfs pre-push \"$@\"";
            }

            Directory.CreateDirectory (Path.GetDirectoryName (pre_push_hook_path));
            File.WriteAllText (pre_push_hook_path, pre_push_hook_content);

            var git_push = new GitCommand (LocalPath, string.Format ("push --all --progress origin", RemoteUrl), auth_info);
            git_push.StartInfo.RedirectStandardError = true;
            git_push.Start ();

            if (!ReadStream (git_push))
                return false;

            git_push.WaitForExit ();

            UpdateSizes ();

            if (git_push.ExitCode == 0)
                return true;

            Error = ErrorStatus.HostUnreachable;
            return false;
        }
Example #20
0
        List<ChangeSet> GetChangeSetsInternal(string path)
        {
            var change_sets = new List <ChangeSet> ();
            GitCommand git;

            if (path == null) {
                git = new GitCommand (LocalPath, "--no-pager log --since=1.month --raw --find-renames --date=iso " +
                    "--format=medium --no-color --no-merges");

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

                git = new GitCommand (LocalPath, "--no-pager log --raw --find-renames --date=iso " +
                    "--format=medium --no-color --no-merges -- \"" + path + "\"");
            }

            string output = git.StartAndReadStandardOutput ();

            if (path == null && string.IsNullOrWhiteSpace (output)) {
                git = new GitCommand (LocalPath, "--no-pager log -n 75 --raw --find-renames --date=iso " +
                    "--format=medium --no-color --no-merges");

                output = git.StartAndReadStandardOutput ();
            }

            string [] lines      = output.Split ("\n".ToCharArray ());
            List<string> entries = new List <string> ();

            // Split up commit entries
            int line_number = 0;
            bool first_pass = true;
            string entry = "", last_entry = "";
            foreach (string line in lines) {
                if (line.StartsWith ("commit") && !first_pass) {
                    entries.Add (entry);
                    entry = "";
                    line_number = 0;

                } else {
                    first_pass = false;
                }

                // Only parse first 250 files to prevent memory issues
                if (line_number < 250) {
                    entry += line + "\n";
                    line_number++;
                }

                last_entry = entry;
            }

            entries.Add (last_entry);

            // Parse commit entries
            foreach (string log_entry in entries) {
                Match match = this.log_regex.Match (log_entry);

                if (!match.Success) {
                    match = this.merge_regex.Match (log_entry);

                    if (!match.Success)
                        continue;
                }

                ChangeSet change_set = new ChangeSet ();

                change_set.Folder    = new SparkleFolder (Name);
                change_set.Revision  = match.Groups [1].Value;
                change_set.User      = new User (match.Groups [2].Value, match.Groups [3].Value);
                change_set.RemoteUrl = RemoteUrl;

                change_set.Timestamp = new DateTime (int.Parse (match.Groups [4].Value),
                    int.Parse (match.Groups [5].Value), int.Parse (match.Groups [6].Value),
                    int.Parse (match.Groups [7].Value), int.Parse (match.Groups [8].Value),
                    int.Parse (match.Groups [9].Value));

                string time_zone     = match.Groups [10].Value;
                int our_offset       = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Hours;
                int their_offset     = int.Parse (time_zone.Substring (0, 3));
                change_set.Timestamp = change_set.Timestamp.AddHours (their_offset * -1);
                change_set.Timestamp = change_set.Timestamp.AddHours (our_offset);

                string [] entry_lines = log_entry.Split ("\n".ToCharArray ());

                // Parse file list. Lines containing file changes start with ":"
                foreach (string entry_line in entry_lines) {
                    // Skip lines containing backspace characters
                    if (!entry_line.StartsWith (":") || entry_line.Contains ("\\177"))
                        continue;

                    string file_path = entry_line.Substring (39);

                    if (file_path.Equals (".sparkleshare"))
                        continue;

                    string type_letter    = entry_line [37].ToString ();
                    bool change_is_folder = false;

                    if (file_path.EndsWith (".empty")) {
                        file_path        = file_path.Substring (0, file_path.Length - ".empty".Length);
                        change_is_folder = true;
                    }

                    try {
                        file_path = EnsureSpecialCharacters (file_path);

                    } catch (Exception e) {
                        Logger.LogInfo ("Local", "Error parsing file name '" + file_path + "'", e);
                        continue;
                    }

                    file_path = file_path.Replace ("\\\"", "\"");

                    Change change = new Change () {
                        Path      = file_path,
                        IsFolder  = change_is_folder,
                        Timestamp = change_set.Timestamp,
                        Type      = ChangeType.Added
                    };

                    if (type_letter.Equals ("R")) {
                        int tab_pos         = entry_line.LastIndexOf ("\t");
                        file_path           = entry_line.Substring (42, tab_pos - 42);
                        string to_file_path = entry_line.Substring (tab_pos + 1);

                        try {
                            file_path = EnsureSpecialCharacters (file_path);

                        } catch (Exception e) {
                            Logger.LogInfo ("Local", "Error parsing file name '" + file_path + "'", e);
                            continue;
                        }

                        try {
                            to_file_path = EnsureSpecialCharacters (to_file_path);

                        } catch (Exception e) {
                            Logger.LogInfo ("Local", "Error parsing file name '" + to_file_path + "'", e);
                            continue;
                        }

                        file_path    = file_path.Replace ("\\\"", "\"");
                        to_file_path = to_file_path.Replace ("\\\"", "\"");

                        if (file_path.EndsWith (".empty")) {
                            file_path = file_path.Substring (0, file_path.Length - 6);
                            change_is_folder = true;
                        }

                        if (to_file_path.EndsWith (".empty")) {
                            to_file_path = to_file_path.Substring (0, to_file_path.Length - 6);
                            change_is_folder = true;
                        }

                        change.Path        = file_path;
                        change.MovedToPath = to_file_path;
                        change.Type        = ChangeType.Moved;

                    } else if (type_letter.Equals ("M")) {
                        change.Type = ChangeType.Edited;

                    } else if (type_letter.Equals ("D")) {
                        change.Type = ChangeType.Deleted;
                    }

                    change_set.Changes.Add (change);
                }

                // Group commits per user, per day
                if (change_sets.Count > 0 && path == null) {
                    ChangeSet last_change_set = change_sets [change_sets.Count - 1];

                    if (change_set.Timestamp.Year  == last_change_set.Timestamp.Year &&
                        change_set.Timestamp.Month == last_change_set.Timestamp.Month &&
                        change_set.Timestamp.Day   == last_change_set.Timestamp.Day &&
                        change_set.User.Name.Equals (last_change_set.User.Name)) {

                        last_change_set.Changes.AddRange (change_set.Changes);

                        if (DateTime.Compare (last_change_set.Timestamp, change_set.Timestamp) < 1) {
                            last_change_set.FirstTimestamp = last_change_set.Timestamp;
                            last_change_set.Timestamp      = change_set.Timestamp;
                            last_change_set.Revision       = change_set.Revision;

                        } else {
                            last_change_set.FirstTimestamp = change_set.Timestamp;
                        }

                    } else {
                        change_sets.Add (change_set);
                    }

                } else {
                    // Don't show removals or moves in the revision list of a file
                    if (path != null) {
                        List<Change> changes_to_skip = new List<Change> ();

                        foreach (Change change in change_set.Changes) {
                            if ((change.Type == ChangeType.Deleted || change.Type == ChangeType.Moved)
                                && change.Path.Equals (path)) {

                                changes_to_skip.Add (change);
                            }
                        }

                        foreach (Change change_to_skip in changes_to_skip)
                            change_set.Changes.Remove (change_to_skip);
                    }

                    change_sets.Add (change_set);
                }
            }

            return change_sets;
        }
        void InstallConfiguration()
        {
            string [] settings = {
                "core.autocrlf input",
                "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.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"
            };

            if (InstallationInfo.OperatingSystem == OS.Windows)
                settings [0] = "core.autocrlf true";

            foreach (string setting in settings) {
                var git_config = new GitCommand (TargetFolder, "config " + setting);
                git_config.StartAndWaitForExit ();
            }
        }
Example #22
0
        // Merges the fetched changes
        bool Merge()
        {
            string message = FormatCommitMessage();

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

            GitCommand git;

            // Stop if we're already in a merge because something went wrong
            if (this.in_merge)
            {
                git = new GitCommand(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 GitCommand(LocalPath, "config core.ignorecase true");
            git.StartAndWaitForExit();

            git = new GitCommand(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;
                    Logger.LogInfo("Git", Name + " | Error status changed to " + Error);

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

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

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

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

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

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

            return(true);
        }
        void InstallGitLFS()
        {
            var git_config_required = new GitCommand (TargetFolder, "config filter.lfs.required true");

            string GIT_SSH_COMMAND = GitCommand.FormatGitSSHCommand (auth_info);
            string smudge_command;
            string clean_command;

            if (InstallationInfo.OperatingSystem == OS.Mac) {
                smudge_command = "env GIT_SSH_COMMAND='" + GIT_SSH_COMMAND + "' " +
                    Path.Combine (Configuration.DefaultConfiguration.BinPath, "git-lfs") + " smudge %f";

                clean_command = Path.Combine (Configuration.DefaultConfiguration.BinPath, "git-lfs") + " clean %f";

            } else {
                smudge_command = "env GIT_SSH_COMMAND='" + GIT_SSH_COMMAND + "' git-lfs smudge %f";
                clean_command = "git-lfs clean %f";
            }

            var git_config_smudge = new GitCommand (TargetFolder,
                string.Format ("config filter.lfs.smudge \"{0}\"", smudge_command));

            var git_config_clean = new GitCommand (TargetFolder,
                string.Format ("config filter.lfs.clean '{0}'", clean_command));

            git_config_required.StartAndWaitForExit ();
            git_config_clean.StartAndWaitForExit ();
            git_config_smudge.StartAndWaitForExit ();
        }
Example #24
0
        List <ChangeSet> GetChangeSetsInternal(string path)
        {
            var        change_sets = new List <ChangeSet> ();
            GitCommand git;

            if (path == null)
            {
                git = new GitCommand(LocalPath, "--no-pager log --since=1.month --raw --find-renames --date=iso " +
                                     "--format=medium --no-color --no-merges");
            }
            else
            {
                path = path.Replace("\\", "/");

                git = new GitCommand(LocalPath, "--no-pager log --raw --find-renames --date=iso " +
                                     "--format=medium --no-color --no-merges -- \"" + path + "\"");
            }

            string output = git.StartAndReadStandardOutput();

            if (path == null && string.IsNullOrWhiteSpace(output))
            {
                git = new GitCommand(LocalPath, "--no-pager log -n 75 --raw --find-renames --date=iso " +
                                     "--format=medium --no-color --no-merges");

                output = git.StartAndReadStandardOutput();
            }

            string []     lines   = output.Split("\n".ToCharArray());
            List <string> entries = new List <string> ();

            // Split up commit entries
            int    line_number = 0;
            bool   first_pass = true;
            string entry = "", last_entry = "";

            foreach (string line in lines)
            {
                if (line.StartsWith("commit") && !first_pass)
                {
                    entries.Add(entry);
                    entry       = "";
                    line_number = 0;
                }
                else
                {
                    first_pass = false;
                }

                // Only parse first 250 files to prevent memory issues
                if (line_number < 250)
                {
                    entry += line + "\n";
                    line_number++;
                }

                last_entry = entry;
            }

            entries.Add(last_entry);

            // Parse commit entries
            foreach (string log_entry in entries)
            {
                Match match = this.log_regex.Match(log_entry);

                if (!match.Success)
                {
                    match = this.merge_regex.Match(log_entry);

                    if (!match.Success)
                    {
                        continue;
                    }
                }

                ChangeSet change_set = new ChangeSet();

                change_set.Folder   = new SparkleFolder(Name);
                change_set.Revision = match.Groups [1].Value;
                change_set.User     = new User(match.Groups [2].Value, match.Groups [3].Value);

                if (change_set.User.Name == "SparkleShare")
                {
                    continue;
                }

                change_set.RemoteUrl = RemoteUrl;

                if (StorageType == StorageType.Encrypted)
                {
                    string password_file_path = Path.Combine(LocalPath, ".git", "info", "encryption_password");
                    string password           = File.ReadAllText(password_file_path);

                    try {
                        change_set.User = new User(
                            change_set.User.Name.AESDecrypt(password),
                            change_set.User.Email.AESDecrypt(password));
                    } catch (Exception e) {
                        Console.WriteLine(e.StackTrace);
                        change_set.User = new User(match.Groups [2].Value, match.Groups [3].Value);
                    }
                }

                change_set.Timestamp = new DateTime(int.Parse(match.Groups [4].Value),
                                                    int.Parse(match.Groups [5].Value), int.Parse(match.Groups [6].Value),
                                                    int.Parse(match.Groups [7].Value), int.Parse(match.Groups [8].Value),
                                                    int.Parse(match.Groups [9].Value));

                string time_zone    = match.Groups [10].Value;
                int    our_offset   = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Hours;
                int    their_offset = int.Parse(time_zone.Substring(0, 3));
                change_set.Timestamp = change_set.Timestamp.AddHours(their_offset * -1);
                change_set.Timestamp = change_set.Timestamp.AddHours(our_offset);

                string [] entry_lines = log_entry.Split("\n".ToCharArray());

                // Parse file list. Lines containing file changes start with ":"
                foreach (string entry_line in entry_lines)
                {
                    // Skip lines containing backspace characters
                    if (!entry_line.StartsWith(":") || entry_line.Contains("\\177"))
                    {
                        continue;
                    }

                    string file_path = entry_line.Substring(39);

                    if (file_path.Equals(".sparkleshare"))
                    {
                        continue;
                    }

                    string type_letter      = entry_line [37].ToString();
                    bool   change_is_folder = false;

                    if (file_path.EndsWith(".empty"))
                    {
                        file_path        = file_path.Substring(0, file_path.Length - ".empty".Length);
                        change_is_folder = true;
                    }

                    try {
                        file_path = EnsureSpecialCharacters(file_path);
                    } catch (Exception e) {
                        Logger.LogInfo("Local", "Error parsing file name '" + file_path + "'", e);
                        continue;
                    }

                    file_path = file_path.Replace("\\\"", "\"");

                    Change change = new Change()
                    {
                        Path      = file_path,
                        IsFolder  = change_is_folder,
                        Timestamp = change_set.Timestamp,
                        Type      = ChangeType.Added
                    };

                    if (type_letter.Equals("R"))
                    {
                        int tab_pos = entry_line.LastIndexOf("\t");
                        file_path = entry_line.Substring(42, tab_pos - 42);
                        string to_file_path = entry_line.Substring(tab_pos + 1);

                        try {
                            file_path = EnsureSpecialCharacters(file_path);
                        } catch (Exception e) {
                            Logger.LogInfo("Local", "Error parsing file name '" + file_path + "'", e);
                            continue;
                        }

                        try {
                            to_file_path = EnsureSpecialCharacters(to_file_path);
                        } catch (Exception e) {
                            Logger.LogInfo("Local", "Error parsing file name '" + to_file_path + "'", e);
                            continue;
                        }

                        file_path    = file_path.Replace("\\\"", "\"");
                        to_file_path = to_file_path.Replace("\\\"", "\"");

                        if (file_path.EndsWith(".empty"))
                        {
                            file_path        = file_path.Substring(0, file_path.Length - 6);
                            change_is_folder = true;
                        }

                        if (to_file_path.EndsWith(".empty"))
                        {
                            to_file_path     = to_file_path.Substring(0, to_file_path.Length - 6);
                            change_is_folder = true;
                        }

                        change.Path        = file_path;
                        change.MovedToPath = to_file_path;
                        change.Type        = ChangeType.Moved;
                    }
                    else if (type_letter.Equals("M"))
                    {
                        change.Type = ChangeType.Edited;
                    }
                    else if (type_letter.Equals("D"))
                    {
                        change.Type = ChangeType.Deleted;
                    }

                    change_set.Changes.Add(change);
                }

                // Group commits per user, per day
                if (change_sets.Count > 0 && path == null)
                {
                    ChangeSet last_change_set = change_sets [change_sets.Count - 1];

                    if (change_set.Timestamp.Year == last_change_set.Timestamp.Year &&
                        change_set.Timestamp.Month == last_change_set.Timestamp.Month &&
                        change_set.Timestamp.Day == last_change_set.Timestamp.Day &&
                        change_set.User.Name.Equals(last_change_set.User.Name))
                    {
                        last_change_set.Changes.AddRange(change_set.Changes);

                        if (DateTime.Compare(last_change_set.Timestamp, change_set.Timestamp) < 1)
                        {
                            last_change_set.FirstTimestamp = last_change_set.Timestamp;
                            last_change_set.Timestamp      = change_set.Timestamp;
                            last_change_set.Revision       = change_set.Revision;
                        }
                        else
                        {
                            last_change_set.FirstTimestamp = change_set.Timestamp;
                        }
                    }
                    else
                    {
                        change_sets.Add(change_set);
                    }
                }
                else
                {
                    // Don't show removals or moves in the revision list of a file
                    if (path != null)
                    {
                        List <Change> changes_to_skip = new List <Change> ();

                        foreach (Change change in change_set.Changes)
                        {
                            if ((change.Type == ChangeType.Deleted || change.Type == ChangeType.Moved) &&
                                change.Path.Equals(path))
                            {
                                changes_to_skip.Add(change);
                            }
                        }

                        foreach (Change change_to_skip in changes_to_skip)
                        {
                            change_set.Changes.Remove(change_to_skip);
                        }
                    }

                    change_sets.Add(change_set);
                }
            }

            return(change_sets);
        }
Example #25
0
        public override bool SyncUp()
        {
            if (!Add())
            {
                Error = ErrorStatus.UnreadableFiles;
                return(false);
            }

            string message = base.status_message.Replace("\"", "\\\"");

            if (string.IsNullOrEmpty(message))
            {
                message = FormatCommitMessage();
            }

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

            string pre_push_hook_path = Path.Combine(LocalPath, ".git", "hooks", "pre-push");
            string pre_push_hook_content;

            // The pre-push hook may have been changed by Git LFS, overwrite it to use our own configuration
            if (InstallationInfo.OperatingSystem == OS.Mac)
            {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand(auth_info) + "' " +
                    Path.Combine(Configuration.DefaultConfiguration.BinPath, "git-lfs") + " pre-push \"$@\"";
            }
            else
            {
                pre_push_hook_content =
                    "#!/bin/sh" + Environment.NewLine +
                    "env GIT_SSH_COMMAND='" + GitCommand.FormatGitSSHCommand(auth_info) + "' " +
                    "git-lfs pre-push \"$@\"";
            }

            Directory.CreateDirectory(Path.GetDirectoryName(pre_push_hook_path));
            File.WriteAllText(pre_push_hook_path, pre_push_hook_content);

            var git_push = new GitCommand(LocalPath, string.Format("push --all --progress origin", RemoteUrl), auth_info);

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

            if (!ReadStream(git_push))
            {
                return(false);
            }

            git_push.WaitForExit();

            UpdateSizes();

            if (git_push.ExitCode == 0)
            {
                return(true);
            }

            Error = ErrorStatus.HostUnreachable;
            return(false);
        }
Example #26
0
        // Stages the made changes
        bool Add()
        {
            var git = new GitCommand (LocalPath, "add --all");
            git.StartAndWaitForExit ();

            return (git.ExitCode == 0);
        }
Example #27
0
        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

            var    git_status = new GitCommand(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);
                    }
                }

                Logger.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"))
                {
                    Logger.LogInfo("Git", Name + " | Ignoring conflict in special file: " + conflicting_path);

                    // Recover local version
                    var git_ours = new GitCommand(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;
                }

                Logger.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
                    var git_ours = new GitCommand(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
                    var git_theirs = new GitCommand(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
                    var git_add = new GitCommand(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
                    var git_theirs = new GitCommand(LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
                    git_theirs.StartAndWaitForExit();


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

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

            Add();

            var git = new GitCommand(LocalPath,
                                     "commit --message=\"Conflict resolution\" --author=\"SparkleShare <*****@*****.**>\"");

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

            if (trigger_conflict_event)
            {
                OnConflictResolved();
            }
        }
Example #28
0
        List <ChangeSet> GetChangeSetsInternal(string path)
        {
            var        change_sets = new List <ChangeSet> ();
            GitCommand git;

            string log_args = "--since=1.month --name-status --date=iso --find-renames --no-merges --no-color";

            if (path == null)
            {
                git = new GitCommand(LocalPath, "--no-pager log " + log_args);
            }
            else
            {
                path = path.Replace("\\", "/");
                git  = new GitCommand(LocalPath, "--no-pager log " + log_args + " -- \"" + path + "\"");
            }

            string output = git.StartAndReadStandardOutput();

            if (path == null && string.IsNullOrWhiteSpace(output))
            {
                git    = new GitCommand(LocalPath, "--no-pager log -n 75 " + log_args);
                output = git.StartAndReadStandardOutput();
            }


            // Offset the output so our log_regex can be simpler
            string commit_sep = "commit ";

            if (output.StartsWith(commit_sep))
            {
                output = output.Substring(commit_sep.Length) + "\n\n" + commit_sep;
            }


            MatchCollection matches = this.log_regex.Matches(output);

            foreach (Match match in matches)
            {
                ChangeSet change_set = ParseChangeSet(match);

                if (change_set == null)
                {
                    continue;
                }

                int count = 0;
                foreach (string line in match.Groups["files"].Value.Split("\n".ToCharArray()))
                {
                    if (count++ == 256)
                    {
                        break;
                    }

                    Change change = ParseChange(line);

                    if (change == null)
                    {
                        continue;
                    }

                    change.Timestamp = change_set.Timestamp;
                    change_set.Changes.Add(change);
                }


                if (path == null && change_sets.Count > 0)
                {
                    ChangeSet last_change_set = change_sets [change_sets.Count - 1];

                    // If a change set set already exists for this user and day, group into that one
                    if (change_set.Timestamp.Year == last_change_set.Timestamp.Year &&
                        change_set.Timestamp.Month == last_change_set.Timestamp.Month &&
                        change_set.Timestamp.Day == last_change_set.Timestamp.Day &&
                        change_set.User.Name.Equals(last_change_set.User.Name))
                    {
                        last_change_set.Changes.AddRange(change_set.Changes);

                        if (DateTime.Compare(last_change_set.Timestamp, change_set.Timestamp) < 1)
                        {
                            last_change_set.FirstTimestamp = last_change_set.Timestamp;
                            last_change_set.Timestamp      = change_set.Timestamp;
                            last_change_set.Revision       = change_set.Revision;
                        }
                        else
                        {
                            last_change_set.FirstTimestamp = change_set.Timestamp;
                        }
                    }
                    else
                    {
                        change_sets.Add(change_set);
                    }
                }
                else if (path != null)
                {
                    // Don't show removals or moves in the history list of a file
                    var changes = new Change [change_set.Changes.Count];
                    change_set.Changes.CopyTo(changes);

                    foreach (Change change in changes)
                    {
                        if (!change.Path.Equals(path))
                        {
                            continue;
                        }

                        if (change.Type == ChangeType.Deleted || change.Type == ChangeType.Moved)
                        {
                            change_set.Changes.Remove(change);
                        }
                    }

                    change_sets.Add(change_set);
                }
                else
                {
                    change_sets.Add(change_set);
                }
            }

            return(change_sets);
        }
Example #29
0
        public override string Complete(StorageType selected_storage_type)
        {
            string identifier      = base.Complete(selected_storage_type);
            string identifier_path = Path.Combine(TargetFolder, ".sparkleshare");

            InstallConfiguration();
            InstallGitLFS();

            InstallAttributeRules();
            InstallExcludeRules();

            if (IsFetchedRepoEmpty)
            {
                File.WriteAllText(identifier_path, identifier);
                File.SetAttributes(identifier_path, FileAttributes.Hidden);

                // We can't do the "commit --all" shortcut because it doesn't add untracked files
                var git_add    = new GitCommand(TargetFolder, "add .sparkleshare");
                var git_commit = new GitCommand(TargetFolder,
                                                string.Format("commit --message=\"{0}\" --author=\"{1}\"",
                                                              "Set up SparkleShare project",
                                                              "SparkleShare <*****@*****.**>"));

                git_add.StartAndWaitForExit();
                git_commit.StartAndWaitForExit();

                // These branches will be pushed later by "git push --all"
                if (selected_storage_type == StorageType.LargeFiles)
                {
                    var git_branch = new GitCommand(TargetFolder, "branch x-sparkleshare-lfs", auth_info);
                    git_branch.StartAndWaitForExit();
                }

                if (selected_storage_type == StorageType.Encrypted)
                {
                    var git_branch = new GitCommand(TargetFolder,
                                                    string.Format("branch x-sparkleshare-encrypted-{0}", password_salt), auth_info);

                    git_branch.StartAndWaitForExit();
                }
            }
            else
            {
                string branch          = "HEAD";
                string prefered_branch = "SparkleShare";

                // Prefer the "SparkleShare" branch if it exists
                var git_show_ref = new GitCommand(TargetFolder,
                                                  "show-ref --verify --quiet refs/heads/" + prefered_branch);

                git_show_ref.StartAndWaitForExit();

                if (git_show_ref.ExitCode == 0)
                {
                    branch = prefered_branch;
                }

                var git_checkout = new GitCommand(TargetFolder, string.Format("checkout --quiet --force {0}", branch));
                git_checkout.StartAndWaitForExit();

                if (File.Exists(identifier_path))
                {
                    File.SetAttributes(identifier_path, FileAttributes.Hidden);
                    identifier = File.ReadAllText(identifier_path).Trim();
                }
            }

            // git-lfs may leave junk behind
            string git_lfs_tmp_path = Path.Combine(Configuration.DefaultConfiguration.TmpPath, "lfs");

            if (Directory.Exists(git_lfs_tmp_path))
            {
                Directory.Delete(git_lfs_tmp_path, recursive: true);
            }

            return(identifier);
        }
Example #30
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");

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

            // Restore the older file...
            var git = new GitCommand (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 {
                Logger.LogInfo ("Git",
                    Name + " | Could not move \"" + local_file_path + "\" to \"" + target_file_path + "\"");
            }

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

            if (target_file_path.StartsWith (LocalPath))
                new Thread (() => OnFileActivity (null)).Start ();
        }
Example #31
0
        public override bool Fetch()
        {
            if (!base.Fetch())
            {
                return(false);
            }

            StorageType?storage_type = DetermineStorageType();

            if (storage_type == null)
            {
                return(false);
            }

            FetchedRepoStorageType = (StorageType)storage_type;

            string git_clone_command = "clone --progress --no-checkout";

            if (!FetchPriorHistory)
            {
                git_clone_command += " --depth=1";
            }

            if (storage_type == StorageType.LargeFiles)
            {
                git_clone_command = "lfs clone --progress --no-checkout";
            }

            git_clone = new GitCommand(Configuration.DefaultConfiguration.TmpPath,
                                       string.Format("{0} \"{1}\" \"{2}\"", git_clone_command, RemoteUrl, TargetFolder),
                                       auth_info);

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

            StreamReader output_stream = git_clone.StandardError;

            if (FetchedRepoStorageType == StorageType.LargeFiles)
            {
                output_stream = git_clone.StandardOutput;
            }

            double percentage  = 0;
            double speed       = 0;
            string information = "";

            while (!output_stream.EndOfStream)
            {
                string line = output_stream.ReadLine();

                ErrorStatus error = GitCommand.ParseProgress(line, out percentage, out speed, out information);

                if (error != ErrorStatus.None)
                {
                    IsActive = false;
                    git_clone.Kill();
                    git_clone.Dispose();

                    return(false);
                }

                OnProgressChanged(percentage, speed, information);
            }

            git_clone.WaitForExit();

            if (git_clone.ExitCode != 0)
            {
                return(false);
            }

            Thread.Sleep(500);
            OnProgressChanged(100, 0, "");
            Thread.Sleep(500);

            return(true);
        }
Example #32
0
        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

            var    git_status = new GitCommand(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_file_path = line.Substring(3);
                conflicting_file_path = EnsureSpecialChars(conflicting_file_path);
                conflicting_file_path = conflicting_file_path.Trim("\"".ToCharArray());

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

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

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

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

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

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

                    continue;
                }

                Logger.LogInfo("Git", Name + " | Resolving: " + conflicting_file_path);

                // Both the local and server version have been modified
                if (line.StartsWith("UU") || line.StartsWith("AA") ||
                    line.StartsWith("AU") || line.StartsWith("UA"))
                {
                    // Get the author name of the conflicting version
                    var    git_log           = new GitCommand(LocalPath, "log -n 1 FETCH_HEAD --pretty=format:%an " + conflicting_file_path);
                    string other_author_name = git_log.StartAndReadStandardOutput();


                    // Generate distinguishing names for both versions of the file
                    string clue_A = string.Format(" (by {0})", base.local_config.User.Name);
                    string clue_B = string.Format(" (by {0})", other_author_name);

                    if (base.local_config.User.Name == other_author_name)
                    {
                        clue_A = " (A)";
                        clue_B = " (B)";
                    }


                    string file_name_A = Path.GetFileNameWithoutExtension(conflicting_file_path) + clue_A + Path.GetExtension(conflicting_file_path);
                    string file_name_B = Path.GetFileNameWithoutExtension(conflicting_file_path) + clue_B + Path.GetExtension(conflicting_file_path);

                    string abs_conflicting_file_path = Path.Combine(LocalPath, conflicting_file_path);

                    string abs_file_path_A = Path.Combine(Path.GetDirectoryName(abs_conflicting_file_path), file_name_A);
                    string abs_file_path_B = Path.Combine(Path.GetDirectoryName(abs_conflicting_file_path), file_name_B);


                    // Recover local version
                    var git_checkout_A = new GitCommand(LocalPath, "checkout --ours \"" + conflicting_file_path + "\"");
                    git_checkout_A.StartAndWaitForExit();

                    if (File.Exists(abs_conflicting_file_path) && !File.Exists(abs_file_path_A))
                    {
                        File.Move(abs_conflicting_file_path, abs_file_path_A);
                    }


                    // Recover server version
                    var git_checkout_B = new GitCommand(LocalPath, "checkout --theirs \"" + conflicting_file_path + "\"");
                    git_checkout_B.StartAndWaitForExit();

                    if (File.Exists(abs_conflicting_file_path) && !File.Exists(abs_file_path_B))
                    {
                        File.Move(abs_conflicting_file_path, abs_file_path_B);
                    }


                    // Recover original (before both versions diverged)
                    var git_checkout = new GitCommand(LocalPath, "checkout ORIG_HEAD^ \"" + conflicting_file_path + "\"");
                    git_checkout.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
                    var git_add = new GitCommand(LocalPath, "add \"" + conflicting_file_path + "\"");
                    git_add.StartAndWaitForExit();


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


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

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

            Add();

            var git = new GitCommand(LocalPath,
                                     "commit --message=\"Conflict resolution\" --author=\"SparkleShare <*****@*****.**>\"");

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

            HasUnsyncedChanges = true;

            if (trigger_conflict_event)
            {
                OnConflictResolved();
            }
        }