SparkleShare/SparkleLib/SparkleRepo.cs

1014 lines
35 KiB
C#
Raw Normal View History

// 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
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// 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/>.
2011-02-23 01:13:54 +00:00
using System;
using System.Collections.Generic;
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;
namespace SparkleLib {
public enum SyncStatus {
Idle,
SyncUp,
SyncDown,
Error
}
public class SparkleRepo {
2011-04-20 14:02:20 +00:00
public readonly SparkleBackend Backend;
public readonly string LocalPath;
public readonly string Name;
2011-05-18 18:12:45 +00:00
2011-05-18 18:31:43 +00:00
protected SyncStatus status;
2011-05-18 18:12:45 +00:00
protected string revision;
2011-05-18 18:31:43 +00:00
protected bool is_buffering = false;
protected bool is_polling = true;
protected bool server_online = true;
2011-05-18 18:12:45 +00:00
private Timer remote_timer;
private Timer local_timer;
private FileSystemWatcher watcher;
private SparkleListenerBase listener;
private List <double> sizebuffer;
private bool has_changed = false;
private Object change_lock = new Object ();
2011-05-18 18:12:45 +00:00
// TODO: make this a regexp
public string Url {
get {
SparkleGit git = new SparkleGit (LocalPath, "config --get remote.origin.url");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
return output.TrimEnd ();
}
}
// TODO: make this a regexp
public string Domain {
get {
string domain = Url.Substring (Url.IndexOf ("@") + 1);
if (domain.Contains (":"))
return domain = domain.Substring (0, domain.IndexOf (":"));
else
return domain = domain.Substring (0, domain.IndexOf ("/"));
}
}
public string RemoteName {
get {
return Path.GetFileNameWithoutExtension (Url);
}
}
2011-04-20 14:02:20 +00:00
2011-05-17 00:29:51 +00:00
public string Revision {
2011-04-20 14:02:20 +00:00
get {
2011-05-17 00:29:51 +00:00
return this.revision;
2011-04-20 14:02:20 +00:00
}
}
2011-04-20 14:02:20 +00:00
public bool IsBuffering {
get {
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 IsPolling {
get {
return this.is_polling;
2011-04-20 14:02:20 +00:00
}
}
public bool HasUnsyncedChanges {
2011-04-20 14:02:20 +00:00
get {
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath,
".git", "has_unsynced_changes");
return File.Exists (unsynced_file_path);
}
set {
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath,
".git", "has_unsynced_changes");
if (value) {
if (!File.Exists (unsynced_file_path))
File.Create (unsynced_file_path);
} else {
File.Delete (unsynced_file_path);
}
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 {
return this.server_online;
2011-04-20 14:02:20 +00:00
}
}
2010-07-24 14:03:58 +00:00
public SyncStatus Status {
get {
return this.status;
}
}
2011-05-17 10:43:02 +00:00
public delegate void SyncStatusChangedEventHandler (SyncStatus new_status);
public event SyncStatusChangedEventHandler SyncStatusChanged;
public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set, string source_path);
public delegate void ConflictResolvedEventHandler ();
public delegate void ChangesDetectedEventHandler ();
2011-04-20 14:02:20 +00:00
public event NewChangeSetEventHandler NewChangeSet;
public event ConflictResolvedEventHandler ConflictResolved;
2011-04-20 14:02:20 +00:00
public event ChangesDetectedEventHandler ChangesDetected;
public SparkleRepo (string path, SparkleBackend backend)
2011-04-20 14:02:20 +00:00
{
2011-05-18 18:12:45 +00:00
LocalPath = path;
Name = Path.GetFileName (LocalPath);
Backend = backend;
SyncStatusChanged += delegate (SyncStatus status) {
this.status = status;
};
if (IsEmpty)
2011-05-17 00:29:51 +00:00
this.revision = null;
else
2011-05-17 00:29:51 +00:00
this.revision = GetRevision ();
2011-05-17 00:29:51 +00:00
if (this.revision == null)
2011-05-18 18:12:45 +00:00
CreateInitialChangeSet ();
2011-04-20 14:02:20 +00:00
// Watch the repository's folder
this.watcher = new FileSystemWatcher (LocalPath) {
2011-04-20 14:02:20 +00:00
IncludeSubdirectories = true,
EnableRaisingEvents = true,
Filter = "*"
};
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
NotificationServerType server_type;
2011-04-20 14:02:20 +00:00
if (UsesNotificationCenter)
server_type = NotificationServerType.Central;
2011-04-20 14:02:20 +00:00
else
server_type = NotificationServerType.Own;
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
this.remote_timer = new Timer () {
2011-04-20 14:02:20 +00:00
Interval = 60000
};
this.remote_timer.Elapsed += delegate {
if (this.is_polling) {
2011-04-20 14:02:20 +00:00
CheckForRemoteChanges ();
if (!this.listener.IsConnected)
this.listener.Connect ();
2011-04-20 14:02:20 +00:00
}
if (HasUnsyncedChanges)
2011-04-20 14:02:20 +00:00
FetchRebaseAndPush ();
};
// Stop polling when the connection to the irc channel is succesful
this.listener.Connected += delegate {
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
if (HasUnsyncedChanges)
2011-04-20 14:02:20 +00:00
Push ();
};
// Start polling when the connection to the irc channel is lost
this.listener.Disconnected += delegate {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Falling back to polling");
this.is_polling = true;
2011-04-20 14:02:20 +00:00
};
// Fetch changes when there is a message in the irc channel
this.listener.RemoteChange += delegate (string change_id) {
2011-05-17 00:29:51 +00:00
if (!change_id.Equals (this.revision) && change_id.Length == 40) {
if (Status != SyncStatus.SyncUp &&
Status != SyncStatus.SyncDown &&
!this.is_buffering) {
while (this.listener.ChangesQueue > 0) {
2011-05-18 22:18:11 +00:00
SyncDown ();
this.listener.DecrementChangesQueue ();
2011-04-20 14:02:20 +00:00
}
}
}
};
// Start listening
this.listener.Connect ();
2011-04-20 14:02:20 +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
this.local_timer = new Timer () {
2011-04-20 14:02:20 +00:00
Interval = 250
};
this.local_timer.Elapsed += delegate (object o, ElapsedEventArgs args) {
2011-04-20 14:02:20 +00:00
CheckForChanges ();
};
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-17 00:29:51 +00:00
if (this.revision == null)
this.revision = GetRevision ();
2011-04-20 14:02:20 +00:00
}
2010-07-20 21:21:37 +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, "rev-list --reverse HEAD");
git.Start ();
// Reading the standard output HAS to go before
// WaitForExit, or it will hang forever on output > 4096 bytes
string output = git.StandardOutput.ReadToEnd ();
git.WaitForExit ();
return output.Substring (0, 40);
}
}
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");
2010-07-20 21:21:37 +00:00
2011-04-20 14:02:20 +00:00
git.Start ();
git.WaitForExit ();
if (git.ExitCode != 0)
return;
string remote_revision = git.StandardOutput.ReadToEnd ().TrimEnd ();
if (!remote_revision.StartsWith (this.revision)) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found. (" + remote_revision + ")");
2011-05-18 22:18:11 +00:00
SyncDown ();
}
2011-04-20 14:02:20 +00:00
}
2010-07-20 21:21:37 +00:00
2011-04-20 14:02:20 +00:00
private void CheckForChanges ()
{
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);
this.sizebuffer.Add (CalculateFolderSize (dir_info));
2011-04-20 14:02:20 +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.");
this.is_buffering = false;
this.has_changed = false;
2011-04-20 14:02:20 +00:00
while (AnyDifferences) {
this.watcher.EnableRaisingEvents = false;
2011-04-20 14:02:20 +00:00
AddCommitAndPush ();
this.watcher.EnableRaisingEvents = true;
2011-04-20 14:02:20 +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;
if (AnyDifferences) {
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".
if (!this.has_changed) {
2011-04-20 14:02:20 +00:00
if (ChangesDetected != null)
ChangesDetected ();
2011-04-20 14:02:20 +00:00
}
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + fse_args.Name + "'");
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes found, checking if settled.");
this.remote_timer.Stop ();
2011-04-20 14:02:20 +00:00
lock (this.change_lock) {
this.has_changed = true;
2011-04-20 14:02:20 +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 {
this.local_timer.Stop ();
this.remote_timer.Stop ();
2011-04-20 14:02:20 +00:00
if (AnyDifferences) {
Add ();
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 {
2011-05-17 00:17:40 +00:00
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle); // TODO: in checklocalforchanges
2011-04-20 14:02:20 +00:00
}
} finally {
this.remote_timer.Start ();
this.local_timer.Start ();
2011-04-20 14:02:20 +00:00
}
}
public void FetchRebaseAndPush ()
{
CheckForRemoteChanges ();
Push ();
}
2011-04-20 14:02:20 +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 ();
string output = git.StandardOutput.ReadToEnd ().TrimEnd ();
string [] lines = output.Split ("\n".ToCharArray ());
foreach (string line in lines) {
if (line.Length > 1 && !line [1].Equals (" "))
return true;
}
return false;
2011-04-20 14:02:20 +00:00
}
}
2010-07-20 21:21:37 +00:00
private bool IsEmpty {
get {
SparkleGit git = new SparkleGit (LocalPath, "log -1");
git.Start ();
git.WaitForExit ();
return (git.ExitCode != 0);
}
}
2011-05-17 00:29:51 +00:00
private string GetRevision ()
{
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);
SparkleGit git = new SparkleGit (LocalPath, "log -1 --format=%H");
git.Start ();
git.WaitForExit ();
2010-07-20 21:21:37 +00:00
2011-05-17 10:43:02 +00:00
string output = git.StandardOutput.ReadToEnd ();
string revision = output.Trim ();
2011-05-17 10:43:02 +00:00
return revision;
2011-04-20 14:02:20 +00:00
}
2011-04-20 14:02:20 +00:00
// Stages the made changes
private void Add ()
{
SparkleGit git = new SparkleGit (LocalPath, "add --all");
git.Start ();
git.WaitForExit ();
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged");
2011-04-20 14:02:20 +00:00
}
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 ()
{
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-07-20 21:21:37 +00:00
2011-04-20 14:02:20 +00:00
// Commits the made changes
2011-05-18 22:18:11 +00:00
private void Commit (string message)
2011-04-20 14:02:20 +00:00
{
if (!AnyDifferences)
return;
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-17 00:29:51 +00:00
this.revision = GetRevision ();
SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message + " (" + this.revision + ")");
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
2011-05-18 22:18:11 +00:00
public virtual void SyncDownBase ()
2011-04-20 14:02:20 +00:00
{
2011-05-18 22:18:11 +00:00
SparkleHelpers.DebugInfo ("Sync", "[" + Name + "] Initiated");
this.remote_timer.Stop ();
2010-10-07 21:43:08 +00:00
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.SyncDown);
2011-05-18 22:18:11 +00:00
if (SyncDown ()) {
SparkleHelpers.DebugInfo ("Sync", "[" + Name + "] Done");
this.server_online = true;
this.revision = GetRevision ();
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
2011-05-18 22:18:11 +00:00
} else {
SparkleHelpers.DebugInfo ("Sync", "[" + Name + "] Error");
this.server_online = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
}
2010-07-20 21:21:37 +00:00
2011-05-18 22:18:11 +00:00
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.remote_timer.Start ();
2011-04-20 14:02:20 +00:00
}
2011-05-18 22:18:11 +00:00
// Fetches changes from the remote repository
public bool SyncDown ()
{
SparkleGit git = new SparkleGit (LocalPath, "fetch -v origin master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode == 0) {
Rebase ();
return true;
} else {
return false;
}
}
2011-04-20 14:02:20 +00:00
// Merges the fetched changes
2011-05-18 22:18:11 +00:00
private void Rebase ()
2011-04-20 14:02:20 +00:00
{
2011-05-18 22:18:11 +00:00
DisableWatching ();
2011-04-20 14:02:20 +00:00
if (AnyDifferences) {
Add ();
string commit_message = FormatCommitMessage ();
Commit (commit_message);
}
SparkleGit git = new SparkleGit (LocalPath, "rebase -v FETCH_HEAD");
git.Start ();
git.WaitForExit ();
2010-07-20 21:21:37 +00:00
if (git.ExitCode != 0) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict detected. Trying to get out...");
DisableWatching ();
while (AnyDifferences)
ResolveConflict ();
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
EnableWatching ();
if (ConflictResolved != null)
ConflictResolved ();
Push ();
}
2011-03-03 12:08:50 +00:00
2011-05-17 00:29:51 +00:00
this.revision = GetRevision ();
if (NewChangeSet != null)
NewChangeSet (GetChangeSets (1) [0], LocalPath);
2011-05-18 22:18:11 +00:00
EnableWatching ();
2011-04-20 14:02:20 +00:00
}
2010-07-21 23:17:20 +00:00
2011-04-28 11:49:14 +00:00
private void ResolveConflict ()
{
// 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 ();
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) {
string conflicting_path = line.Substring (3);
conflicting_path = conflicting_path.Trim ("\"".ToCharArray ());
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict type: " + line);
// 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
// Append a timestamp to local version
string timestamp = DateTime.Now.ToString ("HH:mm MMM d");
2011-05-18 18:12:45 +00:00
string their_path = conflicting_path + " (" + GetConfigItem ("sparkleshare.user.name") + ", " + 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
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,
"checkout --ours \"" + conflicting_path + "\"");
2011-04-28 11:49:14 +00:00
git_ours.Start ();
git_ours.WaitForExit ();
Add ();
2011-04-28 11:49:14 +00:00
SparkleGit git_rebase_continue = new SparkleGit (LocalPath, "rebase --continue");
git_rebase_continue.Start ();
git_rebase_continue.WaitForExit ();
}
// 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 ()
{
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing changes");
2011-04-20 14:02:20 +00:00
SparkleGit git = new SparkleGit (LocalPath, "push origin master");
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.SyncUp);
git.Start ();
git.WaitForExit ();
if (git.ExitCode != 0) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes not pushed");
HasUnsyncedChanges = true;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
FetchRebaseAndPush ();
} else {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed");
HasUnsyncedChanges = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.listener.Announce (this.revision);
}
2011-04-20 14:02:20 +00:00
}
2010-08-08 19:16:48 +00:00
2010-07-21 23:17:20 +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 repository's description
private string GetDescription ()
{
string description_file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "description");
2011-04-20 14:02:20 +00:00
if (!File.Exists (description_file_path))
return null;
2011-04-20 14:02:20 +00:00
StreamReader reader = new StreamReader (description_file_path);
string description = reader.ReadToEnd ();
reader.Close ();
2011-04-20 14:02:20 +00:00
if (description.StartsWith ("Unnamed"))
description = null;
2011-04-20 14:02:20 +00:00
return description;
}
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;
}
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-04-20 14:02:20 +00:00
double size = 0;
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-04-20 14:02:20 +00:00
foreach (FileInfo file in parent.GetFiles()) {
if (!file.Exists)
return 0;
2011-04-20 14:02:20 +00:00
size += file.Length;
}
2011-04-20 14:02:20 +00:00
foreach (DirectoryInfo directory in parent.GetDirectories())
size += CalculateFolderSize (directory);
2011-04-20 14:02:20 +00:00
return size;
}
2011-05-18 18:12:45 +00:00
// Create an initial change set when the
// user has fetched an empty remote folder
protected virtual void CreateInitialChangeSet ()
2011-04-20 14:02:20 +00:00
{
2011-05-18 18:12:45 +00:00
string file_path = Path.Combine (LocalPath, "SparkleShare.txt");
TextWriter writer = new StreamWriter (file_path);
2011-04-20 14:02:20 +00:00
writer.WriteLine (":)");
writer.Close ();
}
2011-05-18 18:12:45 +00:00
public string GetConfigItem (string name)
{
if (String.Compare (name, "sparkleshare.user.name", true) == 0)
return GetUserName ();
else if (String.Compare (name, "sparkleshare.user.email", true) == 0)
return GetUserEmail ();
else
return null;
}
// Returns a list of the latest change sets
2011-04-20 14:02:20 +00:00
// TODO: Method needs to be made a lot faster
2011-05-18 18:12:45 +00:00
public virtual List <SparkleChangeSet> GetChangeSets (int count)
2011-04-20 14:02:20 +00:00
{
if (count < 1)
count = 30;
List <SparkleChangeSet> change_sets = new List <SparkleChangeSet> ();
2011-04-20 14:02:20 +00:00
SparkleGit git_log = new SparkleGit (LocalPath, "log -" + count + " --raw -M --date=iso");
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);
Regex merge_regex = new Regex (@"commit ([a-z0-9]{40})\n" +
"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);
Regex non_merge_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);
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: ")) {
regex = merge_regex;
2011-04-20 14:02:20 +00:00
is_merge_commit = true;
} else {
regex = non_merge_regex;
2011-04-20 14:02:20 +00:00
}
Match match = regex.Match (log_entry);
if (match.Success) {
SparkleChangeSet change_set = new SparkleChangeSet ();
2011-04-20 14:02:20 +00:00
change_set.Revision = match.Groups [1].Value;
change_set.UserName = match.Groups [2].Value;
change_set.UserEmail = match.Groups [3].Value;
change_set.IsMerge = is_merge_commit;
2011-04-20 14:02:20 +00:00
change_set.Timestamp = new DateTime (int.Parse (match.Groups [4].Value),
2011-04-20 14:02:20 +00:00
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);
string to_file_path;
2011-04-20 14:02:20 +00:00
if (change_type.Equals ("A")) {
change_set.Added.Add (file_path);
2011-04-20 14:02:20 +00:00
} else if (change_type.Equals ("M")) {
change_set.Edited.Add (file_path);
2011-04-20 14:02:20 +00:00
} else if (change_type.Equals ("D")) {
change_set.Deleted.Add (file_path);
} 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);
change_set.MovedFrom.Add (file_path);
change_set.MovedTo.Add (to_file_path);
2011-04-20 14:02:20 +00:00
}
}
}
change_sets.Add (change_set);
2011-04-20 14:02:20 +00:00
}
}
2010-08-08 19:16:48 +00:00
return change_sets;
2011-04-20 14:02:20 +00:00
}
2010-08-29 10:38:34 +00:00
2011-04-20 14:02:20 +00:00
// Creates a pretty commit message based on what has changed
private string FormatCommitMessage ()
{
List<string> Added = new List<string> ();
List<string> Modified = new List<string> ();
List<string> Removed = new List<string> ();
string file_name = "";
string message = "";
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
string output = git_status.StandardOutput.ReadToEnd ().Trim ("\n".ToCharArray ());
git_status.WaitForExit ();
string [] lines = output.Split ("\n".ToCharArray ());
foreach (string line in lines) {
if (line.StartsWith ("A"))
Added.Add (line.Substring (3));
else if (line.StartsWith ("M"))
Modified.Add (line.Substring (3));
else if (line.StartsWith ("D"))
Removed.Add (line.Substring (3));
else if (line.StartsWith ("R")) {
Removed.Add (line.Substring (3, (line.IndexOf (" -> ") - 3)));
Added.Add (line.Substring (line.IndexOf (" -> ") + 4));
}
}
int count = 0;
int max_count = 20;
string n = Environment.NewLine;
foreach (string added in Added) {
file_name = added.Trim ("\"".ToCharArray ());
message += "+ " + file_name + "" + n;
2011-04-20 14:02:20 +00:00
count++;
if (count == max_count)
return message + "...";
2011-04-20 14:02:20 +00:00
}
foreach (string modified in Modified) {
file_name = modified.Trim ("\"".ToCharArray ());
message += "/ " + file_name + "" + n;
2011-04-20 14:02:20 +00:00
count++;
if (count == max_count)
return message + "...";
2011-04-20 14:02:20 +00:00
}
foreach (string removed in Removed) {
file_name = removed.Trim ("\"".ToCharArray ());
message += "- " + file_name + "" + n;
2011-04-20 14:02:20 +00:00
count++;
if (count == max_count)
return message + "..." + n;
2011-04-20 14:02:20 +00:00
}
return message.TrimEnd ();
2011-04-20 14:02:20 +00:00
}
public static bool IsRepo (string path)
{
return System.IO.Directory.Exists (Path.Combine (path, ".git"));
}
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-04-20 14:02:20 +00:00
// Disposes all resourses of this object
public void Dispose ()
2011-04-20 14:02:20 +00:00
{
this.remote_timer.Dispose ();
this.local_timer.Dispose ();
this.listener.Dispose ();
2011-04-20 14:02:20 +00:00
}
}
}