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 (); }
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 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(); } }
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(); }
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(); } }
// 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); }
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; }
// 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 (); }
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; }
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; }
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; }
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; }
// 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; }
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; }
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 (); } }
// 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 (); }
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); }
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); }
// Stages the made changes bool Add() { var git = new GitCommand (LocalPath, "add --all"); git.StartAndWaitForExit (); return (git.ExitCode == 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(); } }
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); }
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); }
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 (); }
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); }
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(); } }