summaryrefslogtreecommitdiff
path: root/SparkleLib/Git/SparkleRepoGit.cs
diff options
context:
space:
mode:
Diffstat (limited to 'SparkleLib/Git/SparkleRepoGit.cs')
-rw-r--r--SparkleLib/Git/SparkleRepoGit.cs276
1 files changed, 166 insertions, 110 deletions
diff --git a/SparkleLib/Git/SparkleRepoGit.cs b/SparkleLib/Git/SparkleRepoGit.cs
index 100340a..26eeee9 100644
--- a/SparkleLib/Git/SparkleRepoGit.cs
+++ b/SparkleLib/Git/SparkleRepoGit.cs
@@ -2,9 +2,9 @@
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -12,16 +12,16 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
using SparkleLib;
namespace SparkleLib.Git {
@@ -36,25 +36,29 @@ namespace SparkleLib.Git {
private Regex progress_regex = new Regex (@"([0-9]+)%", RegexOptions.Compiled);
private Regex speed_regex = new Regex (@"([0-9\.]+) ([KM])iB/s", RegexOptions.Compiled);
-
- private Regex log_regex = new Regex (@"commit ([a-z0-9]{40})\n" +
- "Author: (.+) <(.+)>\n" +
- "*" +
- "Date: ([0-9]{4})-([0-9]{2})-([0-9]{2}) " +
- "([0-9]{2}):([0-9]{2}):([0-9]{2}) (.[0-9]{4})\n" +
- "*", RegexOptions.Compiled);
+
+ private Regex log_regex = new Regex (@"commit ([a-f0-9]{40})*\n" +
+ "Author: (.+) <(.+)>\n" +
+ "Date: ([0-9]{4})-([0-9]{2})-([0-9]{2}) " +
+ "([0-9]{2}):([0-9]{2}):([0-9]{2}) (.[0-9]{4})\n" +
+ "*", RegexOptions.Compiled);
+
+ private Regex merge_regex = new Regex (@"commit ([a-f0-9]{40})\n" +
+ "Merge: [a-f0-9]{7} [a-f0-9]{7}\n" +
+ "Author: (.+) <(.+)>\n" +
+ "Date: ([0-9]{4})-([0-9]{2})-([0-9]{2}) " +
+ "([0-9]{2}):([0-9]{2}):([0-9]{2}) (.[0-9]{4})\n" +
+ "*", RegexOptions.Compiled);
private string branch {
get {
if (!string.IsNullOrEmpty (this.cached_branch))
return this.cached_branch;
- string rebase_apply_path = new string [] { LocalPath, ".git", "rebase-apply" }.Combine ();
-
SparkleGit git = new SparkleGit (LocalPath, "config core.ignorecase true");
git.StartAndWaitForExit ();
- while (Directory.Exists (rebase_apply_path) && HasLocalChanges) {
+ while (this.in_merge && HasLocalChanges) {
try {
ResolveConflict ();
@@ -74,6 +78,14 @@ namespace SparkleLib.Git {
}
+ private bool in_merge {
+ get {
+ string merge_file_path = new string [] { LocalPath, ".git", "MERGE_HEAD" }.Combine ();
+ return File.Exists (merge_file_path);
+ }
+ }
+
+
public SparkleRepo (string path, SparkleConfig config) : base (path, config)
{
SparkleGit git = new SparkleGit (LocalPath, "config core.ignorecase false");
@@ -126,7 +138,7 @@ namespace SparkleLib.Git {
public override double Size {
get {
- string file_path = new string [] { LocalPath, ".git", "repo_size" }.Combine ();
+ string file_path = new string [] { LocalPath, ".git", "info", "size" }.Combine ();
try {
string size = File.ReadAllText (file_path);
@@ -141,7 +153,7 @@ namespace SparkleLib.Git {
public override double HistorySize {
get {
- string file_path = new string [] { LocalPath, ".git", "repo_history_size" }.Combine ();
+ string file_path = new string [] { LocalPath, ".git", "info", "history_size" }.Combine ();
try {
string size = File.ReadAllText (file_path);
@@ -159,8 +171,8 @@ namespace SparkleLib.Git {
double size = CalculateSizes (new DirectoryInfo (LocalPath));
double history_size = CalculateSizes (new DirectoryInfo (Path.Combine (LocalPath, ".git")));
- string size_file_path = new string [] { LocalPath, ".git", "repo_size" }.Combine ();
- string history_size_file_path = new string [] { LocalPath, ".git", "repo_history_size" }.Combine ();
+ string size_file_path = new string [] { LocalPath, ".git", "info", "size" }.Combine ();
+ string history_size_file_path = new string [] { LocalPath, ".git", "info", "history_size" }.Combine ();
File.WriteAllText (size_file_path, size.ToString ());
File.WriteAllText (history_size_file_path, history_size.ToString ());
@@ -223,7 +235,10 @@ namespace SparkleLib.Git {
return false;
}
- string message = FormatCommitMessage ();
+ string message = base.status_message.Replace ("\"", "\\\"");
+
+ if (string.IsNullOrEmpty (message))
+ message = FormatCommitMessage ();
if (message != null)
Commit (message);
@@ -390,7 +405,7 @@ namespace SparkleLib.Git {
UpdateSizes ();
if (git.ExitCode == 0) {
- if (Rebase ()) {
+ if (Merge ()) {
ClearCache ();
return true;
@@ -467,7 +482,7 @@ namespace SparkleLib.Git {
// Merges the fetched changes
- private bool Rebase ()
+ private bool Merge ()
{
string message = FormatCommitMessage ();
@@ -477,14 +492,13 @@ namespace SparkleLib.Git {
}
SparkleGit git;
- string rebase_apply_path = new string [] { LocalPath, ".git", "rebase-apply" }.Combine ();
-
- // Stop if we're already in a rebase because something went wrong
- if (Directory.Exists (rebase_apply_path)) {
- git = new SparkleGit (LocalPath, "rebase --abort");
- git.StartAndWaitForExit ();
- return false;
+ // Stop if we're already in a merge because something went wrong
+ if (this.in_merge) {
+ git = new SparkleGit (LocalPath, "merge --abort");
+ git.StartAndWaitForExit ();
+
+ return false;
}
// Temporarily change the ignorecase setting to true to avoid
@@ -492,19 +506,19 @@ namespace SparkleLib.Git {
git = new SparkleGit (LocalPath, "config core.ignorecase true");
git.StartAndWaitForExit ();
- git = new SparkleGit (LocalPath, "rebase FETCH_HEAD");
+ git = new SparkleGit (LocalPath, "merge FETCH_HEAD");
git.StartInfo.RedirectStandardOutput = false;
string error_output = git.StartAndReadStandardError ();
if (git.ExitCode != 0) {
- // Stop when we can't rebase due to locked local files
+ // Stop when we can't merge due to locked local files
// error: cannot stat 'filename': Permission denied
if (error_output.Contains ("error: cannot stat")) {
Error = ErrorStatus.UnreadableFiles;
SparkleLogger.LogInfo ("Git", Name + " | Error status changed to " + Error);
- git = new SparkleGit (LocalPath, "rebase --abort");
+ git = new SparkleGit (LocalPath, "merge --abort");
git.StartAndWaitForExit ();
git = new SparkleGit (LocalPath, "config core.ignorecase false");
@@ -513,20 +527,19 @@ namespace SparkleLib.Git {
return false;
} else {
- SparkleLogger.LogInfo ("", error_output);
+ SparkleLogger.LogInfo ("Git", error_output);
SparkleLogger.LogInfo ("Git", Name + " | Conflict detected, trying to get out...");
- while (Directory.Exists (rebase_apply_path) && HasLocalChanges) {
+ while (this.in_merge && HasLocalChanges) {
try {
ResolveConflict ();
- } catch (IOException e) {
+ } catch (Exception e) {
SparkleLogger.LogInfo ("Git", Name + " | Failed to resolve conflict, trying again...", e);
}
}
SparkleLogger.LogInfo ("Git", Name + " | Conflict resolved");
- OnConflictResolved ();
}
}
@@ -550,71 +563,76 @@ namespace SparkleLib.Git {
// AA unmerged, both added -> Use server's, save ours as a timestamped copy
// UU unmerged, both modified -> Use server's, save ours as a timestamped copy
// ?? unmerged, new files -> Stage the new files
- //
- // Note that a rebase merge works by replaying each commit from the working branch on
- // top of the upstream branch. Because of this, when a merge conflict happens the
- // side reported as 'ours' is the so-far rebased series, starting with upstream,
- // and 'theirs' is the working branch. In other words, the sides are swapped.
- //
- // So: 'ours' means the 'server's version' and 'theirs' means the 'local version' after this comment
SparkleGit git_status = new SparkleGit (LocalPath, "status --porcelain");
string output = git_status.StartAndReadStandardOutput ();
string [] lines = output.Split ("\n".ToCharArray ());
- bool changes_added = false;
+ bool trigger_conflict_event = false;
foreach (string line in lines) {
string conflicting_path = line.Substring (3);
conflicting_path = EnsureSpecialCharacters (conflicting_path);
conflicting_path = conflicting_path.Trim ("\"".ToCharArray ());
+ // Remove possible rename indicators
+ string [] separators = {" -> \"", " -> "};
+ foreach (string separator in separators) {
+ if (conflicting_path.Contains (separator)) {
+ conflicting_path = conflicting_path.Substring (
+ conflicting_path.IndexOf (separator) + separator.Length);
+ }
+ }
+
SparkleLogger.LogInfo ("Git", Name + " | Conflict type: " + line);
- // Ignore conflicts in the .sparkleshare file and use the local version
+ // Ignore conflicts in hidden files and use the local versions
if (conflicting_path.EndsWith (".sparkleshare") || conflicting_path.EndsWith (".empty")) {
SparkleLogger.LogInfo ("Git", Name + " | Ignoring conflict in special file: " + conflicting_path);
// Recover local version
- SparkleGit git_theirs = new SparkleGit (LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
- git_theirs.StartAndWaitForExit ();
+ SparkleGit git_ours = new SparkleGit (LocalPath, "checkout --ours \"" + conflicting_path + "\"");
+ git_ours.StartAndWaitForExit ();
- File.SetAttributes (Path.Combine (LocalPath, conflicting_path), FileAttributes.Hidden);
- changes_added = true;
+ string abs_conflicting_path = Path.Combine (LocalPath, conflicting_path);
+ if (File.Exists (abs_conflicting_path))
+ File.SetAttributes (abs_conflicting_path, FileAttributes.Hidden);
+
continue;
}
- SparkleLogger.LogInfo ("Git", Name + " | Resolving: " + line);
+ SparkleLogger.LogInfo ("Git", Name + " | Resolving: " + conflicting_path);
// Both the local and server version have been modified
if (line.StartsWith ("UU") || line.StartsWith ("AA") ||
line.StartsWith ("AU") || line.StartsWith ("UA")) {
// Recover local version
- SparkleGit git_theirs = new SparkleGit (LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
- git_theirs.StartAndWaitForExit ();
+ SparkleGit git_ours = new SparkleGit (LocalPath, "checkout --ours \"" + conflicting_path + "\"");
+ git_ours.StartAndWaitForExit ();
// Append a timestamp to local version.
// Windows doesn't allow colons in the file name, so
// we use "h" between the hours and minutes instead.
string timestamp = DateTime.Now.ToString ("MMM d H\\hmm");
- string their_path = Path.GetFileNameWithoutExtension (conflicting_path) +
+ 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_their_path = Path.Combine (LocalPath, their_path);
+ string abs_our_path = Path.Combine (LocalPath, our_path);
- if (File.Exists (abs_conflicting_path) && !File.Exists (abs_their_path))
- File.Move (abs_conflicting_path, abs_their_path);
+ if (File.Exists (abs_conflicting_path) && !File.Exists (abs_our_path))
+ File.Move (abs_conflicting_path, abs_our_path);
// Recover server version
- SparkleGit git_ours = new SparkleGit (LocalPath, "checkout --ours \"" + conflicting_path + "\"");
- git_ours.StartAndWaitForExit ();
+ SparkleGit git_theirs = new SparkleGit (LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
+ git_theirs.StartAndWaitForExit ();
- changes_added = true;
+ trigger_conflict_event = true;
- // The local version has been modified, but the server version was removed
+
+ // 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.
@@ -622,17 +640,15 @@ namespace SparkleLib.Git {
SparkleGit git_add = new SparkleGit (LocalPath, "add \"" + conflicting_path + "\"");
git_add.StartAndWaitForExit ();
- changes_added = true;
- // The server version has been modified, but the local version was removed
+ // The local version has been modified, but the server version was removed
} else if (line.StartsWith ("UD")) {
// Recover server version
- SparkleGit git_theirs = new SparkleGit (LocalPath, "checkout --ours \"" + conflicting_path + "\"");
+ SparkleGit git_theirs = new SparkleGit (LocalPath, "checkout --theirs \"" + conflicting_path + "\"");
git_theirs.StartAndWaitForExit ();
- changes_added = true;
-
+
// Server and local versions were removed
} else if (line.StartsWith ("DD")) {
SparkleLogger.LogInfo ("Git", Name + " | No need to resolve: " + line);
@@ -640,7 +656,6 @@ namespace SparkleLib.Git {
// New local files
} else if (line.StartsWith ("??")) {
SparkleLogger.LogInfo ("Git", Name + " | Found new file, no need to resolve: " + line);
- changes_added = true;
} else {
SparkleLogger.LogInfo ("Git", Name + " | Don't know what to do with: " + line);
@@ -648,15 +663,13 @@ namespace SparkleLib.Git {
}
Add ();
- SparkleGit git;
-
- if (changes_added)
- git = new SparkleGit (LocalPath, "rebase --continue");
- else
- git = new SparkleGit (LocalPath, "rebase --skip");
+ SparkleGit git = new SparkleGit (LocalPath, "commit --message \"Conflict resolution by SparkleShare\"");
git.StartInfo.RedirectStandardOutput = false;
git.StartAndWaitForExit ();
+
+ if (trigger_conflict_event)
+ OnConflictResolved ();
}
@@ -721,15 +734,20 @@ namespace SparkleLib.Git {
Error = ErrorStatus.HostIdentityChanged;
} else if (line.StartsWith ("Permission denied") ||
- line.StartsWith ("ssh_exchange_identification: Connection closed by remote host")) {
+ line.StartsWith ("ssh_exchange_identification: Connection closed by remote host") ||
+ line.StartsWith ("The authenticity of host")) {
Error = ErrorStatus.AuthenticationFailed;
-
+
} else if (line.EndsWith ("does not appear to be a git repository")) {
- Error = ErrorStatus.NotFound;
+ Error = ErrorStatus.NotFound;
+
+ } else if (line.EndsWith ("expected old/new/ref, got 'shallow")) {
+ Error = ErrorStatus.IncompatibleClientServer;
} else if (line.StartsWith ("error: Disk space exceeded") ||
- line.EndsWith ("No space left on device")) {
+ line.EndsWith ("No space left on device") ||
+ line.EndsWith ("file write error (Disk quota exceeded)")) {
Error = ErrorStatus.DiskSpaceExceeded;
}
@@ -744,6 +762,13 @@ namespace SparkleLib.Git {
}
+ public override List<SparkleChange> UnsyncedChanges {
+ get {
+ return ParseStatus ();
+ }
+ }
+
+
public override List<SparkleChangeSet> GetChangeSets ()
{
return GetChangeSetsInternal (null);
@@ -811,8 +836,12 @@ namespace SparkleLib.Git {
foreach (string log_entry in entries) {
Match match = this.log_regex.Match (log_entry);
- if (!match.Success)
- continue;
+ if (!match.Success) {
+ match = this.merge_regex.Match (log_entry);
+
+ if (!match.Success)
+ continue;
+ }
SparkleChangeSet change_set = new SparkleChangeSet ();
@@ -1065,54 +1094,81 @@ namespace SparkleLib.Git {
}
- // Creates a pretty commit message based on what has changed
- private string FormatCommitMessage ()
+
+ private List<SparkleChange> ParseStatus ()
{
- int count = 0;
- string message = "";
+ List<SparkleChange> changes = new List<SparkleChange> ();
SparkleGit git_status = new SparkleGit (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", "");
+ SparkleChange 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 ());
+ string path = line.Substring (3, line.IndexOf (" -> ") - 3).Trim ("\" ".ToCharArray ());
+ string moved_to_path = line.Substring (line.IndexOf (" -> ") + 4).Trim ("\" ".ToCharArray ());
+
+ change = new SparkleChange () {
+ Type = SparkleChangeType.Moved,
+ Path = EnsureSpecialCharacters (path),
+ MovedToPath = EnsureSpecialCharacters (moved_to_path)
+ };
+
+ } else {
+ string path = line.Substring (2).Trim ("\" ".ToCharArray ());
+ change = new SparkleChange () { Path = EnsureSpecialCharacters (path) };
+ change.Type = SparkleChangeType.Added;
+
+ if (line.StartsWith ("M")) {
+ change.Type = SparkleChangeType.Edited;
+
+ } else if (line.StartsWith ("D")) {
+ change.Type = SparkleChangeType.Deleted;
+ }
+ }
- message += "< ‘" + EnsureSpecialCharacters (path) + "’\n";
- message += "> ‘" + EnsureSpecialCharacters (moved_to_path) + "’\n";
+ changes.Add (change);
+ }
+
+ git_status.StandardOutput.ReadToEnd ();
+ git_status.WaitForExit ();
+
+ return changes;
+ }
+
+
+ // Creates a pretty commit message based on what has changed
+ private string FormatCommitMessage ()
+ {
+ string message = "";
+
+ foreach (SparkleChange change in ParseStatus ()) {
+ if (change.Type == SparkleChangeType.Moved) {
+ message += "< ‘" + EnsureSpecialCharacters (change.Path) + "’\n";
+ message += "> ‘" + EnsureSpecialCharacters (change.MovedToPath) + "’\n";
} else {
- if (line.StartsWith ("M")) {
+ if (change.Type == SparkleChangeType.Edited) {
message += "/";
- } else if (line.StartsWith ("D")) {
+ } else if (change.Type == SparkleChangeType.Deleted) {
message += "-";
- } else {
+ } else if (change.Type == SparkleChangeType.Added) {
message += "+";
}
- string path = line.Substring (3).Trim ("\"".ToCharArray ());
- message += " ‘" + EnsureSpecialCharacters (path) + "’\n";
- }
-
- count++;
- if (count == 10) {
- message += "...\n";
- break;
+ message += " ‘" + change.Path + "’\n";
}
}
- git_status.StandardOutput.ReadToEnd ();
- git_status.WaitForExit ();
-
if (string.IsNullOrWhiteSpace (message))
return null;
else