2010-05-03 00:04:39 +00:00
|
|
|
|
// SparkleShare, an instant update workflow to Git.
|
|
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2010-11-21 12:33:24 +00:00
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2010-05-03 00:04:39 +00:00
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
2010-07-22 21:10:38 +00:00
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-02-23 01:13:54 +00:00
|
|
|
|
|
2010-05-03 00:04:39 +00:00
|
|
|
|
using System;
|
2010-09-04 15:23:20 +00:00
|
|
|
|
using System.Collections.Generic;
|
2010-05-03 00:04:39 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Timers;
|
|
|
|
|
|
2011-03-08 23:55:21 +00:00
|
|
|
|
using Meebey.SmartIrc4net;
|
|
|
|
|
using Mono.Unix;
|
|
|
|
|
|
2010-08-02 15:42:42 +00:00
|
|
|
|
namespace SparkleLib {
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
public enum SyncStatus {
|
|
|
|
|
SyncUpStarted,
|
|
|
|
|
SyncUpFinished,
|
|
|
|
|
SyncUpFailed,
|
|
|
|
|
SyncDownStarted,
|
|
|
|
|
SyncDownFinished,
|
|
|
|
|
SyncDownFailed
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-21 13:46:46 +00:00
|
|
|
|
public class SparkleRepo {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
private Timer remote_timer;
|
|
|
|
|
private Timer local_timer;
|
|
|
|
|
private FileSystemWatcher watcher;
|
|
|
|
|
private System.Object change_lock;
|
2011-05-14 17:10:24 +00:00
|
|
|
|
private SparkleListenerBase listener;
|
2011-05-08 22:01:07 +00:00
|
|
|
|
private List <double> sizebuffer;
|
|
|
|
|
private bool has_changed;
|
|
|
|
|
|
|
|
|
|
private string current_hash;
|
|
|
|
|
private bool is_syncing;
|
|
|
|
|
private bool is_buffering;
|
|
|
|
|
private bool is_polling;
|
|
|
|
|
private bool is_fetching;
|
|
|
|
|
private bool is_pushing;
|
|
|
|
|
private bool has_unsynced_changes;
|
|
|
|
|
private bool server_online;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-08 23:04:53 +00:00
|
|
|
|
public readonly SparkleBackend Backend;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public readonly string Name;
|
|
|
|
|
public readonly string RemoteName;
|
|
|
|
|
public readonly string Domain;
|
|
|
|
|
public readonly string Description;
|
|
|
|
|
public readonly string LocalPath;
|
|
|
|
|
public readonly string RemoteOriginUrl;
|
|
|
|
|
public readonly string UserName;
|
|
|
|
|
public readonly string UserEmail;
|
|
|
|
|
|
|
|
|
|
public string CurrentHash {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.current_hash;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-09-08 22:30:24 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool IsBuffering {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.is_buffering;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool IsPushing {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.is_pushing;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-08-28 18:56:19 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool IsPolling {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.is_polling;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-08-28 18:07:07 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool IsSyncing {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.is_syncing;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-08-28 18:07:07 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool IsFetching {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.is_fetching;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool HasUnsyncedChanges {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.has_unsynced_changes;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-24 14:03:58 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public bool ServerOnline {
|
|
|
|
|
get {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
return this.server_online;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-24 14:03:58 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
public delegate void SyncStatusChangedEventHandler (SyncStatus status);
|
|
|
|
|
public event SyncStatusChangedEventHandler SyncStatusChanged;
|
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public delegate void NewCommitEventHandler (SparkleCommit commit, string repository_path);
|
2011-04-21 14:49:26 +00:00
|
|
|
|
public delegate void ConflictDetectedEventHandler (object o, SparkleEventArgs args);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public delegate void ChangesDetectedEventHandler (object o, SparkleEventArgs args);
|
|
|
|
|
public delegate void CommitEndedUpEmptyEventHandler (object o, SparkleEventArgs args);
|
|
|
|
|
|
|
|
|
|
public event NewCommitEventHandler NewCommit;
|
2011-04-21 14:49:26 +00:00
|
|
|
|
public event ConflictDetectedEventHandler ConflictDetected;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
public event ChangesDetectedEventHandler ChangesDetected;
|
|
|
|
|
public event CommitEndedUpEmptyEventHandler CommitEndedUpEmpty;
|
|
|
|
|
|
|
|
|
|
|
2011-05-08 23:04:53 +00:00
|
|
|
|
public SparkleRepo (string path, SparkleBackend backend)
|
2011-04-20 14:02:20 +00:00
|
|
|
|
{
|
|
|
|
|
LocalPath = path;
|
|
|
|
|
Name = Path.GetFileName (LocalPath);
|
2011-04-20 14:32:34 +00:00
|
|
|
|
RemoteOriginUrl = GetRemoteOriginUrl ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
RemoteName = Path.GetFileNameWithoutExtension (RemoteOriginUrl);
|
|
|
|
|
Domain = GetDomain (RemoteOriginUrl);
|
|
|
|
|
Description = GetDescription ();
|
2011-04-20 14:32:34 +00:00
|
|
|
|
UserName = GetUserName ();
|
|
|
|
|
UserEmail = GetUserEmail ();
|
2011-05-08 23:04:53 +00:00
|
|
|
|
Backend = backend;
|
2011-05-08 22:01:07 +00:00
|
|
|
|
|
|
|
|
|
this.is_syncing = false;
|
|
|
|
|
this.is_buffering = false;
|
|
|
|
|
this.is_polling = true;
|
|
|
|
|
this.is_fetching = false;
|
|
|
|
|
this.is_pushing = false;
|
|
|
|
|
this.server_online = true;
|
|
|
|
|
this.has_changed = false;
|
|
|
|
|
this.change_lock = new Object ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-04-21 21:14:44 +00:00
|
|
|
|
if (IsEmpty)
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.current_hash = null;
|
2011-04-21 21:14:44 +00:00
|
|
|
|
else
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.current_hash = GetCurrentHash ();
|
2011-04-21 21:14:44 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath,
|
|
|
|
|
".git", "has_unsynced_changes");
|
|
|
|
|
|
|
|
|
|
if (File.Exists (unsynced_file_path))
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.has_unsynced_changes = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
else
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.has_unsynced_changes = false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if (this.current_hash == null)
|
2011-04-20 14:02:20 +00:00
|
|
|
|
CreateInitialCommit ();
|
|
|
|
|
|
|
|
|
|
// Watch the repository's folder
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher = new FileSystemWatcher (LocalPath) {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
IncludeSubdirectories = true,
|
|
|
|
|
EnableRaisingEvents = true,
|
|
|
|
|
Filter = "*"
|
|
|
|
|
};
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.Changed += new FileSystemEventHandler (OnFileActivity);
|
|
|
|
|
this.watcher.Created += new FileSystemEventHandler (OnFileActivity);
|
|
|
|
|
this.watcher.Deleted += new FileSystemEventHandler (OnFileActivity);
|
|
|
|
|
this.watcher.Renamed += new RenamedEventHandler (OnFileActivity);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-14 17:10:24 +00:00
|
|
|
|
NotificationServerType server_type;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (UsesNotificationCenter)
|
2011-05-14 17:10:24 +00:00
|
|
|
|
server_type = NotificationServerType.Central;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
else
|
2011-05-14 17:10:24 +00:00
|
|
|
|
server_type = NotificationServerType.Own;
|
|
|
|
|
|
2011-05-14 17:11:31 +00:00
|
|
|
|
this.listener = new SparkleListenerIrc (Domain, Identifier, server_type);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
// ...fetch remote changes every 60 seconds if that fails
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer = new Timer () {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Interval = 60000
|
|
|
|
|
};
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Elapsed += delegate {
|
|
|
|
|
if (this.is_polling) {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
CheckForRemoteChanges ();
|
|
|
|
|
|
2011-05-14 17:10:24 +00:00
|
|
|
|
if (!this.listener.IsConnected)
|
|
|
|
|
this.listener.Connect ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if (this.has_unsynced_changes)
|
2011-04-20 14:02:20 +00:00
|
|
|
|
FetchRebaseAndPush ();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Stop polling when the connection to the irc channel is succesful
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.listener.Connected += delegate {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_polling = false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
// Check for changes manually one more time
|
|
|
|
|
CheckForRemoteChanges ();
|
|
|
|
|
|
|
|
|
|
// Push changes that were made since the last disconnect
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if (this.has_unsynced_changes)
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Push ();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Start polling when the connection to the irc channel is lost
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.listener.Disconnected += delegate {
|
|
|
|
|
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Falling back to polling");
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_polling = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Fetch changes when there is a message in the irc channel
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.listener.RemoteChange += delegate (string change_id) {
|
|
|
|
|
if (!change_id.Equals (this.current_hash) && change_id.Length == 40) {
|
|
|
|
|
if (!this.is_fetching && !this.is_buffering) {
|
|
|
|
|
while (this.listener.ChangesQueue > 0) {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Fetch ();
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.listener.DecrementChangesQueue ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
2011-05-14 17:10:24 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.EnableRaisingEvents = false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Rebase ();
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.EnableRaisingEvents = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Start listening
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.listener.Connect ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.sizebuffer = new List <double> ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
// Keep a timer that checks if there are changes and
|
|
|
|
|
// whether they have settled
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.local_timer = new Timer () {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Interval = 250
|
|
|
|
|
};
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.local_timer.Elapsed += delegate (object o, ElapsedEventArgs args) {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
CheckForChanges ();
|
|
|
|
|
};
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Start ();
|
|
|
|
|
this.local_timer.Start ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
// Add everything that changed
|
|
|
|
|
// since SparkleShare was stopped
|
|
|
|
|
AddCommitAndPush ();
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if (this.current_hash == null)
|
|
|
|
|
this.current_hash = GetCurrentHash ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-05-14 17:11:31 +00:00
|
|
|
|
public string Identifier {
|
|
|
|
|
get {
|
|
|
|
|
|
|
|
|
|
// Because git computes a hash based on content,
|
|
|
|
|
// author, and timestamp; it is unique enough to
|
|
|
|
|
// use the hash of the first commit as an identifier
|
|
|
|
|
// for our folder
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "log --reverse -1 --format=%H");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
return git.StandardOutput.ReadToEnd ().Trim ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
private void CheckForRemoteChanges ()
|
|
|
|
|
{
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Checking for remote changes...");
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "ls-remote origin master");
|
|
|
|
|
|
|
|
|
|
git.Exited += delegate {
|
|
|
|
|
if (git.ExitCode != 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-05-14 17:10:24 +00:00
|
|
|
|
string remote_hash = git.StandardOutput.ReadToEnd ().TrimEnd ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if (!remote_hash.StartsWith (this.current_hash)) {
|
2011-04-30 00:42:48 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found. (" + remote_hash + ")");
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Fetch ();
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.EnableRaisingEvents = false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Rebase ();
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.EnableRaisingEvents = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
}
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
private void CheckForChanges ()
|
|
|
|
|
{
|
2011-05-08 22:01:07 +00:00
|
|
|
|
lock (this.change_lock) {
|
|
|
|
|
if (this.has_changed) {
|
|
|
|
|
if ( this.sizebuffer.Count >= 4)
|
|
|
|
|
this.sizebuffer.RemoveAt (0);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
DirectoryInfo dir_info = new DirectoryInfo (LocalPath);
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.sizebuffer.Add (CalculateFolderSize (dir_info));
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if ( this.sizebuffer [0].Equals ( this.sizebuffer [1]) &&
|
|
|
|
|
this.sizebuffer [1].Equals ( this.sizebuffer [2]) &&
|
|
|
|
|
this.sizebuffer [2].Equals ( this.sizebuffer [3])) {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes have settled.");
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_buffering = false;
|
2011-05-16 20:19:58 +00:00
|
|
|
|
this.has_changed = false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
while (AnyDifferences) {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.EnableRaisingEvents = false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
AddCommitAndPush ();
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.watcher.EnableRaisingEvents = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-23 00:09:44 +00:00
|
|
|
|
|
2010-07-24 14:03:58 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Starts a timer when something changes
|
|
|
|
|
private void OnFileActivity (object o, FileSystemEventArgs fse_args)
|
|
|
|
|
{
|
|
|
|
|
if (fse_args.Name.StartsWith (".git/"))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
WatcherChangeTypes wct = fse_args.ChangeType;
|
|
|
|
|
|
2011-04-21 13:46:46 +00:00
|
|
|
|
if (AnyDifferences) {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_buffering = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
// Only fire the event if the timer has been stopped.
|
|
|
|
|
// This prevents multiple events from being raised whilst "buffering".
|
2011-05-08 22:01:07 +00:00
|
|
|
|
if (!this.has_changed) {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleEventArgs args = new SparkleEventArgs ("ChangesDetected");
|
|
|
|
|
|
|
|
|
|
if (ChangesDetected != null)
|
|
|
|
|
ChangesDetected (this, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + fse_args.Name + "'");
|
|
|
|
|
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes found, checking if settled.");
|
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Stop ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
lock (this.change_lock) {
|
|
|
|
|
this.has_changed = true;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-08-28 18:56:19 +00:00
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// When there are changes we generally want to Add, Commit and Push,
|
|
|
|
|
// so this method does them all with appropriate timers, etc. switched off
|
|
|
|
|
public void AddCommitAndPush ()
|
|
|
|
|
{
|
|
|
|
|
try {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.local_timer.Stop ();
|
|
|
|
|
this.remote_timer.Stop ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
if (AnyDifferences) {
|
|
|
|
|
Add ();
|
2010-08-28 18:56:19 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
string message = FormatCommitMessage ();
|
|
|
|
|
Commit (message);
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
Push ();
|
|
|
|
|
} else {
|
|
|
|
|
SparkleEventArgs args = new SparkleEventArgs ("CommitEndedUpEmpty");
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (CommitEndedUpEmpty != null)
|
|
|
|
|
CommitEndedUpEmpty (this, args);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Start ();
|
|
|
|
|
this.local_timer.Start ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-04-15 00:28:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void FetchRebaseAndPush ()
|
|
|
|
|
{
|
|
|
|
|
CheckForRemoteChanges ();
|
|
|
|
|
Push ();
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-04-21 21:14:44 +00:00
|
|
|
|
private bool AnyDifferences {
|
2011-04-20 14:02:20 +00:00
|
|
|
|
get {
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "status --porcelain");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
2011-04-21 13:46:46 +00:00
|
|
|
|
string output = git.StandardOutput.ReadToEnd ().TrimEnd ();
|
|
|
|
|
string [] lines = output.Split ("\n".ToCharArray ());
|
|
|
|
|
|
|
|
|
|
foreach (string line in lines) {
|
2011-04-28 22:32:16 +00:00
|
|
|
|
if (line.Length > 1 && !line [1].Equals (" "))
|
2011-04-21 13:46:46 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
|
|
|
|
|
2011-04-21 21:14:44 +00:00
|
|
|
|
private bool IsEmpty {
|
|
|
|
|
get {
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "log -1");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
return (git.ExitCode != 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-04-16 23:19:42 +00:00
|
|
|
|
private string GetCurrentHash ()
|
|
|
|
|
{
|
2011-04-28 11:49:14 +00:00
|
|
|
|
// Remove stale rebase-apply files because it
|
|
|
|
|
// makes the method return the wrong hashes.
|
|
|
|
|
string rebase_apply_file = SparkleHelpers.CombineMore (LocalPath, ".git", "rebase-apply");
|
|
|
|
|
if (File.Exists (rebase_apply_file))
|
|
|
|
|
File.Delete (rebase_apply_file);
|
|
|
|
|
|
2011-04-16 23:19:42 +00:00
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "log -1 --format=%H");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
string output = git.StandardOutput.ReadToEnd ();
|
|
|
|
|
string hash = output.Trim ();
|
2011-04-30 00:42:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
return hash;
|
|
|
|
|
}
|
2011-02-10 02:29:07 +00:00
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Stages the made changes
|
|
|
|
|
private void Add ()
|
|
|
|
|
{
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Staging changes...");
|
2010-08-29 10:38:34 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "add --all");
|
2011-04-16 23:30:57 +00:00
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged.");
|
|
|
|
|
}
|
2010-07-22 21:10:38 +00:00
|
|
|
|
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Removes unneeded objects
|
|
|
|
|
private void CollectGarbage ()
|
|
|
|
|
{
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Collecting garbage...");
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "gc");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Garbage collected.");
|
|
|
|
|
}
|
2010-08-05 10:40:12 +00:00
|
|
|
|
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Commits the made changes
|
2011-04-21 13:46:46 +00:00
|
|
|
|
public void Commit (string message)
|
2011-04-20 14:02:20 +00:00
|
|
|
|
{
|
|
|
|
|
if (!AnyDifferences)
|
|
|
|
|
return;
|
2010-09-11 09:52:10 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "commit -m '" + message + "'");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.current_hash = GetCurrentHash ();
|
|
|
|
|
SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message + " (" + this.current_hash + ")");
|
2010-10-07 21:43:08 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Collect garbage pseudo-randomly
|
|
|
|
|
if (DateTime.Now.Second % 10 == 0)
|
|
|
|
|
CollectGarbage ();
|
|
|
|
|
}
|
2010-10-07 21:43:08 +00:00
|
|
|
|
|
2010-10-07 21:31:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Fetches changes from the remote repository
|
|
|
|
|
public void Fetch ()
|
|
|
|
|
{
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_syncing = true;
|
|
|
|
|
this.is_fetching = true;
|
2010-10-07 21:43:08 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Stop ();
|
2010-10-07 21:43:08 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes...");
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "fetch -v origin master");
|
2011-02-26 14:20:32 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
if (SyncStatusChanged != null)
|
|
|
|
|
SyncStatusChanged (SyncStatus.SyncDownStarted);
|
2011-02-26 14:20:32 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git.Exited += delegate {
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched.");
|
2011-02-02 07:39:50 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_syncing = false;
|
|
|
|
|
this.is_fetching = false;
|
|
|
|
|
this.current_hash = GetCurrentHash ();
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (git.ExitCode != 0) {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.server_online = false;
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
if (SyncStatusChanged != null)
|
|
|
|
|
SyncStatusChanged (SyncStatus.SyncDownFailed);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
} else {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.server_online = true;
|
2010-05-23 18:03:05 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
if (SyncStatusChanged != null)
|
|
|
|
|
SyncStatusChanged (SyncStatus.SyncDownFinished);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
2010-05-23 18:03:05 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Start ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
};
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
}
|
2011-03-01 23:42:00 +00:00
|
|
|
|
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Merges the fetched changes
|
|
|
|
|
public void Rebase ()
|
|
|
|
|
{
|
|
|
|
|
if (AnyDifferences) {
|
|
|
|
|
Add ();
|
|
|
|
|
|
|
|
|
|
string commit_message = FormatCommitMessage ();
|
|
|
|
|
Commit (commit_message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Rebasing changes...");
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "rebase -v FETCH_HEAD");
|
|
|
|
|
|
|
|
|
|
git.Exited += delegate {
|
2011-04-28 11:49:14 +00:00
|
|
|
|
if (git.ExitCode != 0) {
|
2011-04-30 00:42:48 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict detected. Trying to get out...");
|
2011-05-14 02:18:38 +00:00
|
|
|
|
DisableWatching ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
while (AnyDifferences)
|
2011-04-28 11:49:14 +00:00
|
|
|
|
ResolveConflict ();
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-28 11:49:14 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
|
2011-05-14 02:18:38 +00:00
|
|
|
|
EnableWatching ();
|
2011-02-26 14:20:32 +00:00
|
|
|
|
|
2011-04-28 11:49:14 +00:00
|
|
|
|
SparkleEventArgs args = new SparkleEventArgs ("ConflictDetected");
|
|
|
|
|
if (ConflictDetected != null)
|
|
|
|
|
ConflictDetected (this, args);
|
2011-05-14 02:18:38 +00:00
|
|
|
|
|
|
|
|
|
Push ();
|
2011-04-28 11:49:14 +00:00
|
|
|
|
}
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.current_hash = GetCurrentHash ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
};
|
2011-03-01 23:42:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
2011-03-03 12:08:50 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.current_hash = GetCurrentHash ();
|
2010-05-08 20:06:59 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (NewCommit != null)
|
|
|
|
|
NewCommit (GetCommits (1) [0], LocalPath);
|
|
|
|
|
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes rebased.");
|
|
|
|
|
}
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-04-28 11:49:14 +00:00
|
|
|
|
private void ResolveConflict ()
|
|
|
|
|
{
|
2011-04-30 00:42:48 +00:00
|
|
|
|
// This is al 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 theirs, save ours as a timestamped copy
|
|
|
|
|
// UD unmerged, deleted by them -> Use ours
|
|
|
|
|
// UA unmerged, added by them -> Use theirs, save ours as a timestamped copy
|
|
|
|
|
// DU unmerged, deleted by us -> Use theirs
|
|
|
|
|
// AA unmerged, both added -> Use theirs, save ours as a timestamped copy
|
|
|
|
|
// UU unmerged, both modified -> Use theirs, save ours as a timestamped copy
|
|
|
|
|
//
|
|
|
|
|
// 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'
|
|
|
|
|
|
2011-04-28 11:49:14 +00:00
|
|
|
|
SparkleGit git_status = new SparkleGit (LocalPath, "status --porcelain");
|
|
|
|
|
git_status.Start ();
|
|
|
|
|
git_status.WaitForExit ();
|
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
string output = git_status.StandardOutput.ReadToEnd ().TrimEnd ();
|
2011-04-28 11:49:14 +00:00
|
|
|
|
string [] lines = output.Split ("\n".ToCharArray ());
|
|
|
|
|
|
|
|
|
|
foreach (string line in lines) {
|
2011-04-30 00:42:48 +00:00
|
|
|
|
string conflicting_path = line.Substring (3);
|
|
|
|
|
conflicting_path = conflicting_path.Trim ("\"".ToCharArray ());
|
|
|
|
|
|
2011-05-06 10:31:30 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict type: " + line);
|
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
// 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.Start ();
|
|
|
|
|
git_theirs.WaitForExit ();
|
2011-04-28 11:49:14 +00:00
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
// Append a timestamp to local version
|
|
|
|
|
string timestamp = DateTime.Now.ToString ("HH:mm MMM d");
|
|
|
|
|
string their_path = conflicting_path + " (" + UserName + ", " + timestamp + ")";
|
|
|
|
|
string abs_conflicting_path = Path.Combine (LocalPath, conflicting_path);
|
|
|
|
|
string abs_their_path = Path.Combine (LocalPath, their_path);
|
2011-04-28 11:49:14 +00:00
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
File.Move (abs_conflicting_path, abs_their_path);
|
|
|
|
|
|
|
|
|
|
// Recover server version
|
2011-04-28 11:49:14 +00:00
|
|
|
|
SparkleGit git_ours = new SparkleGit (LocalPath,
|
2011-04-30 00:42:48 +00:00
|
|
|
|
"checkout --ours \"" + conflicting_path + "\"");
|
2011-04-28 11:49:14 +00:00
|
|
|
|
git_ours.Start ();
|
|
|
|
|
git_ours.WaitForExit ();
|
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
Add ();
|
2011-04-28 11:49:14 +00:00
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
SparkleGit git_rebase_continue = new SparkleGit (LocalPath, "rebase --continue");
|
|
|
|
|
git_rebase_continue.Start ();
|
|
|
|
|
git_rebase_continue.WaitForExit ();
|
|
|
|
|
}
|
2011-04-28 22:32:16 +00:00
|
|
|
|
|
2011-04-30 00:42:48 +00:00
|
|
|
|
// The local version has been modified, but the server version was removed
|
|
|
|
|
if (line.StartsWith ("DU")) {
|
|
|
|
|
|
|
|
|
|
// The modified local version is already in the
|
|
|
|
|
// checkout, so it just needs to be added.
|
|
|
|
|
//
|
|
|
|
|
// We need to specifically mention the file, so
|
|
|
|
|
// we can't reuse the Add () method
|
|
|
|
|
SparkleGit git_add = new SparkleGit (LocalPath,
|
|
|
|
|
"add " + conflicting_path);
|
|
|
|
|
git_add.Start ();
|
|
|
|
|
git_add.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
SparkleGit git_rebase_continue = new SparkleGit (LocalPath, "rebase --continue");
|
|
|
|
|
git_rebase_continue.Start ();
|
|
|
|
|
git_rebase_continue.WaitForExit ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The server version has been modified, but the local version was removed
|
|
|
|
|
if (line.StartsWith ("UD")) {
|
|
|
|
|
|
|
|
|
|
// We can just skip here, the server version is
|
|
|
|
|
// already in the checkout
|
|
|
|
|
SparkleGit git_rebase_skip = new SparkleGit (LocalPath, "rebase --skip");
|
|
|
|
|
git_rebase_skip.Start ();
|
|
|
|
|
git_rebase_skip.WaitForExit ();
|
2011-04-28 11:49:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Pushes the changes to the remote repo
|
|
|
|
|
public void Push ()
|
|
|
|
|
{
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_syncing = true;
|
|
|
|
|
this.is_pushing = true;
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing changes...");
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "push origin master");
|
2010-07-24 12:32:05 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
if (SyncStatusChanged != null)
|
|
|
|
|
SyncStatusChanged (SyncStatus.SyncUpStarted);
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git.Exited += delegate {
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.is_syncing = false;
|
|
|
|
|
this.is_pushing = false;
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (git.ExitCode != 0) {
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing failed.");
|
2010-09-08 22:30:24 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath ,
|
|
|
|
|
".git", "has_unsynced_changes");
|
2010-07-20 21:21:37 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (!File.Exists (unsynced_file_path))
|
|
|
|
|
File.Create (unsynced_file_path);
|
2010-09-14 19:11:55 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.has_unsynced_changes = true;
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
if (SyncStatusChanged != null)
|
|
|
|
|
SyncStatusChanged (SyncStatus.SyncUpFailed);
|
2010-08-28 18:07:07 +00:00
|
|
|
|
|
2011-04-15 00:28:42 +00:00
|
|
|
|
FetchRebaseAndPush ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
} else {
|
|
|
|
|
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed.");
|
2011-04-15 00:28:42 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath ,
|
|
|
|
|
".git", "has_unsynced_changes");
|
2010-08-28 18:07:07 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (File.Exists (unsynced_file_path))
|
|
|
|
|
File.Delete (unsynced_file_path);
|
2010-08-28 18:07:07 +00:00
|
|
|
|
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.has_unsynced_changes = false;
|
2010-10-06 23:54:42 +00:00
|
|
|
|
|
2011-05-16 23:15:23 +00:00
|
|
|
|
if (SyncStatusChanged != null)
|
|
|
|
|
SyncStatusChanged (SyncStatus.SyncDownFinished);
|
2011-04-23 13:56:05 +00:00
|
|
|
|
|
2011-05-14 17:10:24 +00:00
|
|
|
|
this.listener.Announce (this.current_hash);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
2010-10-06 23:54:42 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
};
|
2010-08-28 18:07:07 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
}
|
2010-08-08 19:16:48 +00:00
|
|
|
|
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-05-14 02:18:38 +00:00
|
|
|
|
public void DisableWatching ()
|
|
|
|
|
{
|
|
|
|
|
this.watcher.EnableRaisingEvents = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void EnableWatching ()
|
|
|
|
|
{
|
|
|
|
|
this.watcher.EnableRaisingEvents = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Gets the domain name of a given URL
|
2011-05-16 20:19:58 +00:00
|
|
|
|
// TODO: make this a regex
|
2011-04-20 14:02:20 +00:00
|
|
|
|
private string GetDomain (string url)
|
|
|
|
|
{
|
|
|
|
|
if (url.Equals (""))
|
|
|
|
|
return null;
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
string domain = url.Substring (url.IndexOf ("@") + 1);
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (domain.Contains (":"))
|
|
|
|
|
domain = domain.Substring (0, domain.IndexOf (":"));
|
|
|
|
|
else
|
|
|
|
|
domain = domain.Substring (0, domain.IndexOf ("/"));
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
return domain;
|
|
|
|
|
}
|
2010-07-21 23:17:20 +00:00
|
|
|
|
|
2010-08-14 20:02:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Gets the repository's description
|
|
|
|
|
private string GetDescription ()
|
|
|
|
|
{
|
2011-04-20 14:23:02 +00:00
|
|
|
|
string description_file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "description");
|
2010-08-14 20:02:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (!File.Exists (description_file_path))
|
|
|
|
|
return null;
|
2010-08-14 20:02:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
StreamReader reader = new StreamReader (description_file_path);
|
|
|
|
|
string description = reader.ReadToEnd ();
|
|
|
|
|
reader.Close ();
|
2010-08-14 20:02:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
if (description.StartsWith ("Unnamed"))
|
|
|
|
|
description = null;
|
2010-08-14 20:02:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
return description;
|
|
|
|
|
}
|
2011-04-20 14:32:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string GetRemoteOriginUrl ()
|
|
|
|
|
{
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "config --get remote.origin.url");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
string output = git.StandardOutput.ReadToEnd ();
|
|
|
|
|
string url = output.Trim ();
|
|
|
|
|
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string GetUserName ()
|
|
|
|
|
{
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "config --get user.name");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
string output = git.StandardOutput.ReadToEnd ();
|
|
|
|
|
string user_name = output.Trim ();
|
|
|
|
|
|
|
|
|
|
return user_name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string GetUserEmail ()
|
|
|
|
|
{
|
|
|
|
|
SparkleGit git = new SparkleGit (LocalPath, "config --get user.email");
|
|
|
|
|
git.Start ();
|
|
|
|
|
git.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
string output = git.StandardOutput.ReadToEnd ();
|
|
|
|
|
string user_email = output.Trim ();
|
|
|
|
|
|
|
|
|
|
return user_email;
|
|
|
|
|
}
|
|
|
|
|
|
2010-08-14 20:02:00 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Recursively gets a folder's size in bytes
|
|
|
|
|
private double CalculateFolderSize (DirectoryInfo parent)
|
|
|
|
|
{
|
|
|
|
|
if (!System.IO.Directory.Exists (parent.ToString ()))
|
|
|
|
|
return 0;
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
double size = 0;
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Ignore the temporary 'rebase-apply' directory. This prevents potential
|
|
|
|
|
// crashes when files are being queried whilst the files have already been deleted.
|
|
|
|
|
if (parent.Name.Equals ("rebase-apply"))
|
|
|
|
|
return 0;
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
foreach (FileInfo file in parent.GetFiles()) {
|
|
|
|
|
if (!file.Exists)
|
|
|
|
|
return 0;
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
size += file.Length;
|
|
|
|
|
}
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
foreach (DirectoryInfo directory in parent.GetDirectories())
|
|
|
|
|
size += CalculateFolderSize (directory);
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
return size;
|
|
|
|
|
}
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Create a first commit in case the user has cloned
|
|
|
|
|
// an empty repository
|
|
|
|
|
private void CreateInitialCommit ()
|
|
|
|
|
{
|
|
|
|
|
TextWriter writer = new StreamWriter (Path.Combine (LocalPath, "SparkleShare.txt"));
|
|
|
|
|
writer.WriteLine (":)");
|
|
|
|
|
writer.Close ();
|
|
|
|
|
}
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Returns a list of latest commits
|
|
|
|
|
// TODO: Method needs to be made a lot faster
|
|
|
|
|
public List <SparkleCommit> GetCommits (int count)
|
|
|
|
|
{
|
|
|
|
|
if (count < 1)
|
|
|
|
|
count = 30;
|
|
|
|
|
|
|
|
|
|
List <SparkleCommit> commits = new List <SparkleCommit> ();
|
|
|
|
|
|
2011-04-28 11:46:22 +00:00
|
|
|
|
SparkleGit git_log = new SparkleGit (LocalPath, "log -" + count + " --raw -M --date=iso");
|
2011-04-21 17:45:11 +00:00
|
|
|
|
Console.OutputEncoding = System.Text.Encoding.Unicode;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
git_log.Start ();
|
|
|
|
|
|
|
|
|
|
// Reading the standard output HAS to go before
|
|
|
|
|
// WaitForExit, or it will hang forever on output > 4096 bytes
|
|
|
|
|
string output = git_log.StandardOutput.ReadToEnd ();
|
|
|
|
|
git_log.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
string [] lines = output.Split ("\n".ToCharArray ());
|
|
|
|
|
List <string> entries = new List <string> ();
|
|
|
|
|
|
|
|
|
|
int j = 0;
|
|
|
|
|
string entry = "", last_entry = "";
|
|
|
|
|
foreach (string line in lines) {
|
|
|
|
|
if (line.StartsWith ("commit") && j > 0) {
|
|
|
|
|
entries.Add (entry);
|
|
|
|
|
entry = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry += line + "\n";
|
|
|
|
|
j++;
|
|
|
|
|
|
|
|
|
|
last_entry = entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entries.Add (last_entry);
|
|
|
|
|
|
2011-05-04 23:07:02 +00:00
|
|
|
|
Regex merge_regex = new Regex (@"commit ([a-z0-9]{40})\n" +
|
2011-05-03 17:13:02 +00:00
|
|
|
|
"Merge: .+ .+\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);
|
|
|
|
|
|
2011-05-04 23:07:02 +00:00
|
|
|
|
Regex non_merge_regex = new Regex (@"commit ([a-z0-9]{40})\n" +
|
2011-05-03 17:13:02 +00:00
|
|
|
|
"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);
|
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// TODO: Need to optimise for speed
|
|
|
|
|
foreach (string log_entry in entries) {
|
|
|
|
|
Regex regex;
|
|
|
|
|
bool is_merge_commit = false;
|
|
|
|
|
|
|
|
|
|
if (log_entry.Contains ("\nMerge: ")) {
|
2011-05-03 17:13:02 +00:00
|
|
|
|
regex = merge_regex;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
is_merge_commit = true;
|
|
|
|
|
} else {
|
2011-05-03 17:13:02 +00:00
|
|
|
|
regex = non_merge_regex;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Match match = regex.Match (log_entry);
|
|
|
|
|
|
|
|
|
|
if (match.Success) {
|
|
|
|
|
SparkleCommit commit = new SparkleCommit ();
|
|
|
|
|
|
|
|
|
|
commit.Hash = match.Groups [1].Value;
|
|
|
|
|
commit.UserName = match.Groups [2].Value;
|
|
|
|
|
commit.UserEmail = match.Groups [3].Value;
|
|
|
|
|
commit.IsMerge = is_merge_commit;
|
|
|
|
|
|
|
|
|
|
commit.DateTime = 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 [] entry_lines = log_entry.Split ("\n".ToCharArray ());
|
|
|
|
|
|
|
|
|
|
foreach (string entry_line in entry_lines) {
|
|
|
|
|
if (entry_line.StartsWith (":")) {
|
|
|
|
|
|
|
|
|
|
string change_type = entry_line [37].ToString ();
|
|
|
|
|
string file_path = entry_line.Substring (39);
|
2011-04-28 11:46:22 +00:00
|
|
|
|
string to_file_path;
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
if (change_type.Equals ("A")) {
|
|
|
|
|
commit.Added.Add (file_path);
|
|
|
|
|
} else if (change_type.Equals ("M")) {
|
|
|
|
|
commit.Edited.Add (file_path);
|
|
|
|
|
} else if (change_type.Equals ("D")) {
|
|
|
|
|
commit.Deleted.Add (file_path);
|
2011-04-28 11:46:22 +00:00
|
|
|
|
} else if (change_type.Equals ("R")) {
|
|
|
|
|
int tab_pos = entry_line.LastIndexOf ("\t");
|
|
|
|
|
file_path = entry_line.Substring (42, tab_pos - 42);
|
|
|
|
|
to_file_path = entry_line.Substring (tab_pos + 1);
|
|
|
|
|
|
|
|
|
|
commit.MovedFrom.Add (file_path);
|
|
|
|
|
commit.MovedTo.Add (to_file_path);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-26 23:57:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
commits.Add (commit);
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-08-08 19:16:48 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
return commits;
|
|
|
|
|
}
|
2010-08-29 10:38:34 +00:00
|
|
|
|
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Creates a pretty commit message based on what has changed
|
|
|
|
|
private string FormatCommitMessage ()
|
|
|
|
|
{
|
2011-04-27 16:44:59 +00:00
|
|
|
|
List<string> Added = new List<string> ();
|
|
|
|
|
List<string> Modified = new List<string> ();
|
|
|
|
|
List<string> Removed = new List<string> ();
|
2011-04-21 12:25:28 +00:00
|
|
|
|
string file_name = "";
|
|
|
|
|
string message = null;
|
|
|
|
|
|
|
|
|
|
SparkleGit git_status = new SparkleGit (LocalPath, "status --porcelain");
|
|
|
|
|
git_status.Start ();
|
|
|
|
|
|
|
|
|
|
// Reading the standard output HAS to go before
|
|
|
|
|
// WaitForExit, or it will hang forever on output > 4096 bytes
|
2011-04-21 13:46:46 +00:00
|
|
|
|
string output = git_status.StandardOutput.ReadToEnd ().Trim ("\n".ToCharArray ());
|
2011-04-21 12:25:28 +00:00
|
|
|
|
git_status.WaitForExit ();
|
|
|
|
|
|
|
|
|
|
string [] lines = output.Split ("\n".ToCharArray ());
|
|
|
|
|
foreach (string line in lines) {
|
|
|
|
|
if (line.StartsWith ("A"))
|
|
|
|
|
Added.Add (line.Substring (2));
|
|
|
|
|
else if (line.StartsWith ("M"))
|
|
|
|
|
Modified.Add (line.Substring (2));
|
|
|
|
|
else if (line.StartsWith ("D"))
|
|
|
|
|
Removed.Add (line.Substring (2));
|
2011-04-27 10:04:51 +00:00
|
|
|
|
else if (line.StartsWith ("R")) {
|
|
|
|
|
Removed.Add (line.Substring (3, (line.IndexOf (" -> ") - 3)));
|
|
|
|
|
Added.Add (line.Substring (line.IndexOf (" -> ") + 4));
|
|
|
|
|
}
|
2011-04-21 12:25:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Added.Count > 0) {
|
|
|
|
|
foreach (string added in Added) {
|
2011-04-27 10:04:51 +00:00
|
|
|
|
file_name = added.Trim ("\"".ToCharArray ());
|
2011-04-20 14:02:20 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
message = "+ ‘" + file_name + "’";
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-21 12:25:28 +00:00
|
|
|
|
if (Modified.Count > 0) {
|
|
|
|
|
foreach (string modified in Modified) {
|
2011-04-27 10:04:51 +00:00
|
|
|
|
file_name = modified.Trim ("\"".ToCharArray ());
|
2011-04-20 14:02:20 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
message = "/ ‘" + file_name + "’";
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-21 12:25:28 +00:00
|
|
|
|
if (Removed.Count > 0) {
|
|
|
|
|
foreach (string removed in Removed) {
|
2011-04-27 10:04:51 +00:00
|
|
|
|
file_name = removed.Trim ("\"".ToCharArray ());
|
2011-04-20 14:02:20 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
message = "- ‘" + file_name + "’";
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-21 12:25:28 +00:00
|
|
|
|
int changes_count = (Added.Count +
|
|
|
|
|
Modified.Count +
|
|
|
|
|
Removed.Count);
|
2011-04-20 14:02:20 +00:00
|
|
|
|
|
|
|
|
|
if (changes_count > 1)
|
|
|
|
|
message += " + " + (changes_count - 1);
|
|
|
|
|
|
|
|
|
|
return message;
|
|
|
|
|
}
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2010-10-10 22:04:08 +00:00
|
|
|
|
|
2011-03-22 13:44:52 +00:00
|
|
|
|
public static bool IsRepo (string path)
|
|
|
|
|
{
|
2011-03-12 18:36:03 +00:00
|
|
|
|
return System.IO.Directory.Exists (Path.Combine (path, ".git"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-03-22 13:44:52 +00:00
|
|
|
|
public bool UsesNotificationCenter
|
|
|
|
|
{
|
2011-04-20 14:02:20 +00:00
|
|
|
|
get {
|
|
|
|
|
string file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "disable_notification_center");
|
|
|
|
|
return !File.Exists (file_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-03-22 13:44:52 +00:00
|
|
|
|
|
2010-05-03 00:04:39 +00:00
|
|
|
|
|
2011-04-20 14:02:20 +00:00
|
|
|
|
// Disposes all resourses of this object
|
2011-04-21 13:46:46 +00:00
|
|
|
|
public void Dispose ()
|
2011-04-20 14:02:20 +00:00
|
|
|
|
{
|
2011-05-08 22:01:07 +00:00
|
|
|
|
this.remote_timer.Dispose ();
|
|
|
|
|
this.local_timer.Dispose ();
|
|
|
|
|
this.listener.Dispose ();
|
2011-04-20 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-24 12:32:05 +00:00
|
|
|
|
}
|