SparkleShare/SparkleLib/SparkleRepo.cs

1245 lines
30 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/>.
using Meebey.SmartIrc4net;
using Mono.Unix;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Timers;
namespace SparkleLib {
2010-08-12 23:27:28 +00:00
public class SparkleRepo {
private Process Process;
private Timer RemoteTimer;
private Timer LocalTimer;
private FileSystemWatcher Watcher;
private bool HasChanged;
private DateTime LastChange;
private System.Object ChangeLock;
private int FetchRequests;
2010-09-12 17:46:00 +00:00
private SparkleListener Listener;
/// <summary>
/// The folder name the repository resides in locally
/// </summary>
public readonly string Name;
/// <summary>
/// The folder name the repository resides in remotely
/// </summary>
public readonly string RemoteName;
/// <summary>
/// The domain the remote repository is on
/// </summary>
public readonly string Domain;
/// <summary>
/// The repository's description
/// </summary>
public readonly string Description;
/// <summary>
/// The path where the repository resides locally
/// </summary>
public readonly string LocalPath;
/// <summary>
/// The raw url used to sync with.
/// </summary>
public readonly string RemoteOriginUrl;
private string _CurrentHash;
private string _UserEmail;
private string _UserName;
private bool _IsSyncing;
private bool _IsBuffering;
private bool _IsPolling;
private bool _IsFetching;
private bool _IsPushing;
private bool _HasUnsyncedChanges;
private bool _ServerOnline;
/// <summary>
/// The hash of the last commit done in the repository
/// </summary>
public string CurrentHash {
get {
return _CurrentHash;
}
}
/// <summary>
/// The name of the user
/// </summary>
public string UserName {
get {
return _UserName;
}
set {
SetUserName (value);
}
}
/// <summary>
/// The name of the user
/// </summary>
public string UserEmail {
get {
return _UserEmail;
}
set {
SetUserEmail (value);
}
}
/// <summary>
/// Indicates whether the repository is currently waiting for local changes to settle
/// </summary>
public bool IsBuffering {
get {
return _IsBuffering;
}
}
/// <summary>
/// Indicates whether the repository is currently pushing changes
/// </summary>
public bool IsPushing {
get {
return _IsPushing;
}
}
/// <summary>
/// Indicates whether the repository has fallen back to polling the remote repository,
/// instead of receiving instant notifications
/// </summary>
public bool IsPolling {
get {
return _IsPolling;
}
}
/// <summary>
/// Indicates whether the repository is currently fetching and/or pushing changes
/// </summary>
public bool IsSyncing {
get {
return _IsSyncing;
}
}
/// <summary>
/// Indicates whether the repository is currently fetching remote changes
/// </summary>
public bool IsFetching {
get {
return _IsFetching;
}
}
/// <summary>
/// Indicates whether the repository has local changes that aren't pushed remotely yet
/// </summary>
public bool HasUnsyncedChanges {
get {
return _HasUnsyncedChanges;
}
}
/// <summary>
/// Indicates whether the remote repository is online,
/// this is based on the result of the Fetch method
/// </summary>
public bool ServerOnline {
get {
return _ServerOnline;
}
}
/// <event cref="Added">
/// Raised when local files have been added to the repository's staging area
/// </event>
public delegate void AddedEventHandler (object o, SparkleEventArgs args);
/// <event cref="Commited">
/// Raised when local files have been added to the repository's index
/// </event>
2010-07-21 23:17:20 +00:00
public delegate void CommitedEventHandler (object o, SparkleEventArgs args);
/// <event cref="PushingStarted">
/// Raised when the repository has started pushing changes
/// </event>
public delegate void PushingStartedEventHandler (object o, SparkleEventArgs args);
/// <event cref="PushingFinished">
/// Raised when the repository has finished pushing changes
/// </event>
public delegate void PushingFinishedEventHandler (object o, SparkleEventArgs args);
/// <event cref="PushingFailed">
/// Raised when pushing changes has failed
/// </event>
public delegate void PushingFailedEventHandler (object o, SparkleEventArgs args);
/// <event cref="FetchingStarted">
/// Raised when when the repository has started fetching remote changes
/// </event>
2010-07-22 21:10:38 +00:00
public delegate void FetchingStartedEventHandler (object o, SparkleEventArgs args);
/// <event cref="FetchingFinished">
/// Raised when when the repository has finished fetching remote changes
/// </event>
2010-07-22 21:10:38 +00:00
public delegate void FetchingFinishedEventHandler (object o, SparkleEventArgs args);
/// <event cref="NewCommit">
/// Raised when the repository has received one or multiple new remote commits
/// </event>
public delegate void NewCommitEventHandler (object o, NewCommitArgs args);
/// <event cref="ConflictDetected">
/// Raised when the newly fetched commits are conflicting with local changes
/// </event>
2010-07-24 14:03:58 +00:00
public delegate void ConflictDetectedEventHandler (object o, SparkleEventArgs args);
/// <event cref="ChangesDetected">
/// Raised when local files have changed in the repository's folder
/// </event>
public delegate void ChangesDetectedEventHandler (object o, SparkleEventArgs args);
/// <event cref="CommitEndedUpEmpty">
/// Raised when there were changes made to local files, but the net result after changes have settled
/// ended up the same as before the changes were made.
/// </event>
public delegate void CommitEndedUpEmptyEventHandler (object o, SparkleEventArgs args);
2010-07-21 23:17:20 +00:00
2010-07-24 14:03:58 +00:00
public event AddedEventHandler Added;
public event CommitedEventHandler Commited;
public event PushingStartedEventHandler PushingStarted;
public event PushingFinishedEventHandler PushingFinished;
public event PushingFailedEventHandler PushingFailed;
2010-07-24 14:03:58 +00:00
public event FetchingStartedEventHandler FetchingStarted;
public event FetchingFinishedEventHandler FetchingFinished;
public event NewCommitEventHandler NewCommit;
public event ConflictDetectedEventHandler ConflictDetected;
public event ChangesDetectedEventHandler ChangesDetected;
public event CommitEndedUpEmptyEventHandler CommitEndedUpEmpty;
2010-07-20 21:21:37 +00:00
public SparkleRepo (string path)
{
2010-09-20 18:53:49 +00:00
LocalPath = path;
Name = Path.GetFileName (LocalPath);
2010-07-27 13:49:48 +00:00
Process = new Process () {
EnableRaisingEvents = true
};
Process.StartInfo.FileName = "git";
Process.StartInfo.RedirectStandardOutput = true;
Process.StartInfo.UseShellExecute = false;
2010-07-20 21:21:37 +00:00
Process.StartInfo.WorkingDirectory = LocalPath;
RemoteName = Path.GetFileNameWithoutExtension (RemoteOriginUrl);
RemoteOriginUrl = GetRemoteOriginUrl ();
Domain = GetDomain (RemoteOriginUrl);
Description = GetDescription ();
_UserName = GetUserName ();
_UserEmail = GetUserEmail ();
_CurrentHash = GetCurrentHash ();
_IsSyncing = false;
_IsBuffering = false;
_IsPolling = true;
_IsFetching = false;
_IsPushing = false;
_ServerOnline = true;
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath ,
".git", "has_unsynced_changes");
if (File.Exists (unsynced_file_path))
_HasUnsyncedChanges = true;
else
_HasUnsyncedChanges = false;
if (_CurrentHash == null)
2010-08-08 19:16:48 +00:00
CreateInitialCommit ();
2010-07-21 23:17:20 +00:00
HasChanged = false;
ChangeLock = new System.Object ();
FetchRequests = 0;
// Watch the repository's folder
2010-07-20 21:21:37 +00:00
Watcher = new FileSystemWatcher (LocalPath) {
IncludeSubdirectories = true,
EnableRaisingEvents = true,
Filter = "*"
};
Watcher.Changed += new FileSystemEventHandler (OnFileActivity);
Watcher.Created += new FileSystemEventHandler (OnFileActivity);
Watcher.Deleted += new FileSystemEventHandler (OnFileActivity);
2010-08-29 10:38:34 +00:00
Watcher.Renamed += new RenamedEventHandler (OnFileActivity);
2010-07-24 21:31:24 +00:00
// Fetch remote changes every minute
RemoteTimer = new Timer () {
Interval = 60000
2010-07-20 21:21:37 +00:00
};
// Listen to the irc channel on the server
Listener = new SparkleListener (Domain, "#" + RemoteName, _UserEmail);
RemoteTimer.Elapsed += delegate {
CheckForRemoteChanges ();
if (_HasUnsyncedChanges)
Push ();
if (!Listener.Client.IsConnected) {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Trying to reconnect...");
Listener.Client.Reconnect (true, true);
}
};
// Stop polling when the connection to the irc channel is succesful
2010-09-12 17:46:00 +00:00
Listener.Client.OnConnected += delegate {
// Check for changes manually one more time
CheckForRemoteChanges ();
// Push changes that were made since the last disconnect
if (_HasUnsyncedChanges)
Push ();
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Connected. Now listening...");
RemoteTimer.Stop ();
_IsPolling = false;
};
// Start polling when the connection to the irc channel is lost
2010-09-12 17:46:00 +00:00
Listener.Client.OnDisconnected += delegate {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Lost connection. Falling back to polling...");
CheckForRemoteChanges ();
RemoteTimer.Start ();
_IsPolling = true;
};
// Fetch changes when there is a message in the irc channel
2010-09-20 18:53:49 +00:00
Listener.Client.OnChannelMessage += delegate (object o, IrcEventArgs args) {
SparkleHelpers.DebugInfo ("Irc", "[" + Name + "] Was notified of a remote change.");
2010-09-20 18:53:49 +00:00
if (!args.Data.Message.Equals (_CurrentHash)) {
FetchRequests++;
if (!_IsFetching) {
while (FetchRequests > 0) {
2010-07-21 23:17:20 +00:00
Fetch ();
FetchRequests--;
}
Rebase ();
}
} else {
SparkleHelpers.DebugInfo ("Irc",
"[" + Name + "] False alarm, already up to date. (" + _CurrentHash + ")");
}
};
// Start listening
Listener.ListenForChanges ();
// Keep a timer that checks if there are changes and
2010-07-20 21:21:37 +00:00
// whether they have settled
LocalTimer = new Timer () {
2010-07-20 21:21:37 +00:00
Interval = 4000
};
LocalTimer.Elapsed += delegate (object o, ElapsedEventArgs args) {
2010-07-20 21:21:37 +00:00
CheckForChanges ();
};
if (_IsPolling)
RemoteTimer.Start ();
LocalTimer.Start ();
// Add everything that changed
// since SparkleShare was stopped
AddCommitAndPush ();
if (_CurrentHash == null)
_CurrentHash = GetCurrentHash ();
2010-08-08 19:16:48 +00:00
2010-07-20 21:21:37 +00:00
}
private void CheckForRemoteChanges ()
{
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Checking for remote changes...");
Process process = new Process () {
EnableRaisingEvents = true
};
process.StartInfo.FileName = "git";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WorkingDirectory = LocalPath;
process.StartInfo.Arguments = "ls-remote origin master";
process.Start ();
process.Exited += delegate {
if (process.ExitCode != 0)
return;
string remote_hash = process.StandardOutput.ReadToEnd ();
if (!remote_hash.StartsWith (_CurrentHash)) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found.");
Fetch ();
Rebase ();
}
};
}
2010-07-20 21:21:37 +00:00
private void CheckForChanges ()
{
lock (ChangeLock) {
if (HasChanged) {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes found, checking if settled.");
2010-07-20 21:21:37 +00:00
DateTime now = DateTime.UtcNow;
TimeSpan changed = new TimeSpan (now.Ticks - LastChange.Ticks);
if (changed.TotalMilliseconds > 5000) {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes have settled, adding files...");
_IsBuffering = false;
HasChanged = false;
2010-07-20 21:21:37 +00:00
AddCommitAndPush ();
2010-07-20 21:21:37 +00:00
}
}
}
}
2010-07-20 21:21:37 +00:00
// Starts a timer when something changes
private void OnFileActivity (object o, FileSystemEventArgs fse_args)
{
2010-07-20 21:21:37 +00:00
WatcherChangeTypes wct = fse_args.ChangeType;
2010-08-29 10:38:34 +00:00
if (!ShouldIgnore (fse_args.FullPath)) {
2010-07-20 21:21:37 +00:00
_IsBuffering = true;
// Only fire the event if the timer has been stopped.
// This prevents multiple events from being raised whilst "buffering".
if (!HasChanged) {
2010-07-20 21:21:37 +00:00
SparkleEventArgs args = new SparkleEventArgs ("ChangesDetected");
if (ChangesDetected != null)
ChangesDetected (this, args);
}
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + fse_args.Name + "'");
2010-07-20 21:21:37 +00:00
RemoteTimer.Stop ();
2010-07-20 21:21:37 +00:00
lock (ChangeLock) {
2010-07-24 14:03:58 +00:00
LastChange = DateTime.UtcNow;
HasChanged = true;
2010-07-24 14:03:58 +00:00
}
2010-07-20 21:21:37 +00:00
}
2010-07-20 21:21:37 +00:00
}
2010-07-20 21:21:37 +00:00
// When there are changes we generally want to Add, Commit and Push,
2010-07-20 21:21:37 +00:00
// so this method does them all with appropriate timers, etc. switched off
public void AddCommitAndPush ()
{
2010-07-20 21:21:37 +00:00
try {
2010-07-20 21:21:37 +00:00
LocalTimer.Stop ();
RemoteTimer.Stop ();
Add ();
2010-07-24 14:03:58 +00:00
2010-07-20 21:21:37 +00:00
string message = FormatCommitMessage ();
2010-08-29 10:38:34 +00:00
if (message != null) {
2010-07-24 14:03:58 +00:00
2010-07-20 21:21:37 +00:00
Commit (message);
CheckForRemoteChanges ();
Push ();
2010-07-24 14:03:58 +00:00
} else {
SparkleEventArgs args = new SparkleEventArgs ("CommitEndedUpEmpty");
if (CommitEndedUpEmpty != null)
CommitEndedUpEmpty (this, args);
}
2010-07-20 21:21:37 +00:00
} finally {
if (_IsPolling)
RemoteTimer.Start ();
LocalTimer.Start ();
2010-07-20 21:21:37 +00:00
}
}
2010-07-20 21:21:37 +00:00
// Stages the made changes
private void Add ()
{
2010-07-20 21:21:37 +00:00
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Staging changes...");
2010-07-20 21:21:37 +00:00
Process.StartInfo.Arguments = "add --all";
Process.Start ();
Process.WaitForExit ();
2010-07-20 21:21:37 +00:00
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged.");
2010-07-20 21:21:37 +00:00
2010-07-21 23:17:20 +00:00
SparkleEventArgs args = new SparkleEventArgs ("Added");
if (Added != null)
Added (this, args);
}
2010-07-20 21:21:37 +00:00
// Commits the made changes
2010-07-24 14:03:58 +00:00
public void Commit (string message)
{
2010-07-20 21:21:37 +00:00
2010-08-29 10:38:34 +00:00
Process.StartInfo.Arguments = "status --porcelain";
Process.Start ();
Process.WaitForExit ();
2010-08-29 13:28:01 +00:00
if (Process.StandardOutput.ReadToEnd ().TrimEnd ("\n".ToCharArray ()).Equals (""))
2010-08-29 10:38:34 +00:00
return;
2010-07-24 14:03:58 +00:00
SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message);
2010-07-20 21:21:37 +00:00
2010-07-24 14:03:58 +00:00
Process.StartInfo.Arguments = "commit -m \"" + message + "\"";
Process.Start ();
Process.WaitForExit ();
2010-07-20 21:21:37 +00:00
2010-07-21 23:17:20 +00:00
SparkleEventArgs args = new SparkleEventArgs ("Commited");
2010-07-24 14:03:58 +00:00
args.Message = message;
2010-07-21 23:17:20 +00:00
if (Commited != null)
2010-07-24 14:03:58 +00:00
Commited (this, args);
2010-07-21 23:17:20 +00:00
}
2010-07-20 21:21:37 +00:00
2010-07-24 21:31:24 +00:00
// Fetches changes from the remote repository
public void Fetch ()
{
2010-07-20 21:21:37 +00:00
_IsSyncing = true;
_IsFetching = true;
RemoteTimer.Stop ();
2010-07-20 21:21:37 +00:00
Process process = new Process () {
EnableRaisingEvents = true
};
2010-07-20 21:21:37 +00:00
process.StartInfo.FileName = "git";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WorkingDirectory = LocalPath;
2010-07-22 21:10:38 +00:00
SparkleEventArgs args;
args = new SparkleEventArgs ("FetchingStarted");
2010-07-20 21:21:37 +00:00
if (FetchingStarted != null)
FetchingStarted (this, args);
2010-07-20 21:21:37 +00:00
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes...");
2010-07-27 11:00:01 +00:00
2010-08-14 10:22:49 +00:00
process.StartInfo.Arguments = "fetch -v origin master";
2010-07-20 21:21:37 +00:00
process.Start ();
process.WaitForExit ();
process.Exited += delegate {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched.");
2010-07-20 21:21:37 +00:00
args = new SparkleEventArgs ("FetchingFinished");
2010-07-21 23:17:20 +00:00
_IsSyncing = false;
_IsFetching = false;
if (FetchingFinished != null)
FetchingFinished (this, args);
2010-07-20 21:21:37 +00:00
if (_IsPolling)
RemoteTimer.Start ();
_CurrentHash = GetCurrentHash ();
2010-07-20 21:21:37 +00:00
if (process.ExitCode != 0)
_ServerOnline = false;
else
_ServerOnline = true;
};
2010-07-20 21:21:37 +00:00
}
2010-07-21 23:17:20 +00:00
// Merges the fetched changes
public void Rebase ()
{
Add ();
Watcher.EnableRaisingEvents = false;
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Rebasing changes...");
2010-07-20 21:21:37 +00:00
2010-08-14 10:22:49 +00:00
Process.StartInfo.Arguments = "rebase -v FETCH_HEAD";
Process.WaitForExit ();
Process.Start ();
2010-07-20 21:21:37 +00:00
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes rebased.");
2010-07-20 21:21:37 +00:00
string output = Process.StandardOutput.ReadToEnd ().Trim ();
2010-07-20 21:21:37 +00:00
if (!output.Contains ("up to date")) {
2010-07-20 21:21:37 +00:00
if (output.Contains ("Failed to merge")) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Resolving conflict...");
Process.StartInfo.Arguments = "status";
Process.WaitForExit ();
Process.Start ();
2010-07-20 21:21:37 +00:00
output = Process.StandardOutput.ReadToEnd ().Trim ();
string [] lines = Regex.Split (output, "\n");
2010-07-20 21:21:37 +00:00
foreach (string line in lines) {
2010-07-20 21:21:37 +00:00
if (line.Contains ("needs merge")) {
2010-07-20 21:21:37 +00:00
string problem_file_name = line.Substring (line.IndexOf (": needs merge"));
2010-07-20 21:21:37 +00:00
Process.StartInfo.Arguments = "checkout --ours " + problem_file_name;
Process.WaitForExit ();
Process.Start ();
2010-07-24 14:03:58 +00:00
string timestamp = DateTime.Now.ToString ("H:mm d MMM yyyy");
File.Move (problem_file_name, problem_file_name + " (" + _UserName + ", " + timestamp + ")");
2010-07-24 14:03:58 +00:00
Process.StartInfo.Arguments = "checkout --theirs " + problem_file_name;
Process.WaitForExit ();
Process.Start ();
2010-07-24 14:03:58 +00:00
SparkleEventArgs args = new SparkleEventArgs ("ConflictDetected");
2010-07-24 14:03:58 +00:00
if (ConflictDetected != null)
ConflictDetected (this, args);
}
}
Add ();
Process.StartInfo.Arguments = "rebase --continue";
Process.WaitForExit ();
Process.Start ();
2010-07-20 21:21:37 +00:00
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
2010-07-20 21:21:37 +00:00
Push ();
}
// Get the last commiter
Process.StartInfo.Arguments = "log --format=\"%an\" -1";
Process.Start ();
string author = Process.StandardOutput.ReadToEnd ().Trim ();
// Get the last committer e-mail
Process.StartInfo.Arguments = "log --format=\"%ae\" -1";
Process.Start ();
string email = Process.StandardOutput.ReadToEnd ().Trim ();
// Get the last commit message
Process.StartInfo.Arguments = "log --format=\"%s\" -1";
Process.Start ();
string message = Process.StandardOutput.ReadToEnd ().Trim ();
NewCommitArgs new_commit_args = new NewCommitArgs (author, email, message, Name);
if (NewCommit != null)
2010-07-24 14:03:58 +00:00
NewCommit (this, new_commit_args);
}
Watcher.EnableRaisingEvents = true;
}
2010-07-21 23:17:20 +00:00
// Pushes the changes to the remote repo
public void Push ()
{
2010-07-20 21:21:37 +00:00
_IsSyncing = true;
_IsPushing = true;
SparkleEventArgs args = new SparkleEventArgs ("PushingStarted");
if (PushingStarted != null)
PushingStarted (this, args);
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing changes...");
2010-07-20 21:21:37 +00:00
2010-08-08 19:16:48 +00:00
Process.StartInfo.Arguments = "push origin master";
Process.Start ();
Process.WaitForExit ();
2010-07-20 21:21:37 +00:00
Process.Exited += delegate {
2010-07-20 21:21:37 +00:00
_IsSyncing = false;
_IsPushing = false;
if (Process.ExitCode != 0) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing failed.");
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath ,
".git", "has_unsynced_changes");
if (!File.Exists (unsynced_file_path))
File.Create (unsynced_file_path);
_HasUnsyncedChanges = true;
2010-07-21 23:17:20 +00:00
args = new SparkleEventArgs ("PushingFailed");
if (PushingFailed != null)
PushingFailed (this, args);
} else {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed.");
args = new SparkleEventArgs ("PushingFinished");
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath ,
".git", "has_unsynced_changes");
if (File.Exists (unsynced_file_path))
File.Delete (unsynced_file_path);
_HasUnsyncedChanges = false;
if (PushingFinished != null)
PushingFinished (this, args);
}
2010-07-21 23:17:20 +00:00
};
2010-07-20 21:21:37 +00:00
}
2010-07-20 21:21:37 +00:00
public void Dispose ()
{
2010-09-12 17:46:00 +00:00
RemoteTimer.Dispose ();
LocalTimer.Dispose ();
Listener.Dispose ();
}
2010-08-29 10:38:34 +00:00
// Ignores repos, dotfiles, swap files and the like
private bool ShouldIgnore (string file_path)
{
2010-07-20 21:21:37 +00:00
2010-08-29 10:38:34 +00:00
if (file_path.EndsWith (".lock") ||
2010-08-30 17:20:47 +00:00
file_path.EndsWith ("~") ||
file_path.Contains (".git") ||
file_path.Contains ("/.") ||
file_path.EndsWith (".swp") ||
2010-08-29 10:38:34 +00:00
Directory.Exists (LocalPath + file_path)) {
2010-07-20 21:21:37 +00:00
2010-08-29 10:38:34 +00:00
return true; // Yes, ignore it
2010-07-20 21:21:37 +00:00
} else {
return false;
}
}
2010-07-20 21:21:37 +00:00
2010-07-24 21:31:24 +00:00
// Gets the domain name of a given URL
2010-07-21 23:17:20 +00:00
public string GetDomain (string url)
{
if (url.Equals (""))
2010-08-08 19:16:48 +00:00
return "";
string domain = url.Substring (url.IndexOf ("@") + 1);
2010-07-21 23:17:20 +00:00
if (domain.IndexOf (":") > -1)
domain = domain.Substring (0, domain.IndexOf (":"));
else
domain = domain.Substring (0, domain.IndexOf ("/"));
return domain;
}
// Gets the repository's description
public string GetDescription ()
{
string description_file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "description");
if (!File.Exists (description_file_path))
return null;
StreamReader reader = new StreamReader (description_file_path);
string description = reader.ReadToEnd ();
reader.Close ();
if (description.StartsWith ("Unnamed"))
description = null;
return description;
}
2010-07-21 23:17:20 +00:00
// Gets hash of the current commit
public string GetCurrentHash ()
{
2010-08-08 19:16:48 +00:00
Process process = new Process () {
EnableRaisingEvents = true
};
2010-07-21 23:17:20 +00:00
process.StartInfo.RedirectStandardOutput = true;
2010-08-08 19:16:48 +00:00
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "git";
process.StartInfo.WorkingDirectory = LocalPath;
process.StartInfo.Arguments = "rev-list --max-count=1 HEAD";
2010-07-21 23:17:20 +00:00
process.Start ();
2010-08-08 19:16:48 +00:00
process.WaitForExit ();
2010-07-21 23:17:20 +00:00
2010-08-08 19:16:48 +00:00
string current_hash = process.StandardOutput.ReadToEnd ().Trim ();
if (process.ExitCode != 0)
return null;
else
return current_hash;
2010-07-21 23:17:20 +00:00
}
// Gets the user's name, example: "User Name"
public string GetUserName ()
{
string user_name;
Process process = new Process ();
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "git";
process.StartInfo.WorkingDirectory = LocalPath;
2010-07-21 23:17:20 +00:00
process.StartInfo.Arguments = "config --get user.name";
process.Start ();
user_name = process.StandardOutput.ReadToEnd ().Trim ();
if (user_name.Equals ("")) {
UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName);
if (unix_user_info.RealName.Equals (""))
user_name = "Mysterious Stranger";
2010-07-21 23:17:20 +00:00
else
user_name = unix_user_info.RealName;
}
return user_name;
}
// Gets the user's name, example: "User Name"
private void SetUserName (string user_name)
{
Process process = new Process ();
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "git";
process.StartInfo.WorkingDirectory = LocalPath;
process.StartInfo.Arguments = "config --set user.name \"" + user_name + "\"";
process.Start ();
_UserName = user_name;
}
2010-07-21 23:17:20 +00:00
// Gets the user's email, example: "person@gnome.org"
private string GetUserEmail ()
2010-07-21 23:17:20 +00:00
{
string user_email;
Process process = new Process ();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.FileName = "git";
process.StartInfo.WorkingDirectory = LocalPath;
2010-07-21 23:17:20 +00:00
process.StartInfo.Arguments = "config --get user.email";
process.Start ();
2010-07-21 23:17:20 +00:00
user_email = process.StandardOutput.ReadToEnd ().Trim ();
if (user_email.Equals (""))
user_email = "Unknown Email";
2010-07-21 23:17:20 +00:00
return user_email;
}
// Gets the user's name, example: "User Name"
private void SetUserEmail (string user_email)
{
Process process = new Process ();
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "git";
process.StartInfo.WorkingDirectory = LocalPath;
process.StartInfo.Arguments = "config --set user.email \"" + user_email + "\"";
process.Start ();
_UserEmail = user_email;
}
2010-08-08 19:16:48 +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 ();
}
2010-07-21 23:17:20 +00:00
// Gets the url of the remote repo, example: "ssh://git@git.gnome.org/project"
public string GetRemoteOriginUrl ()
{
string remote_origin_url;
Process process = new Process ();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.FileName = "git";
process.StartInfo.WorkingDirectory = LocalPath;
2010-07-21 23:17:20 +00:00
process.StartInfo.Arguments = "config --get remote.origin.url";
process.Start ();
remote_origin_url = process.StandardOutput.ReadToEnd ().Trim ();
return remote_origin_url;
}
// Returns a list of latest commits
public List <SparkleCommit> GetCommits (int count)
{
if (count < 0)
return null;
List <SparkleCommit> commits = new List <SparkleCommit> ();
Process process = new Process () {
EnableRaisingEvents = true
};
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WorkingDirectory = LocalPath;
// Get the user's timezone
process.StartInfo.FileName = "date";
process.StartInfo.Arguments = "+%z";
process.Start ();
process.WaitForExit ();
string timezone = process.StandardOutput.ReadToEnd ().Trim ();
int unix_timestamp = 0;
// Add the timezone difference in hours when in a positive timezone
if (timezone.StartsWith ("+"))
2010-09-08 13:33:27 +00:00
unix_timestamp = 3600 * int.Parse (timezone.Substring (1, 2));
// Remove the timezone difference in hours when in a negative timezone
if (timezone.StartsWith ("-"))
2010-09-08 13:33:27 +00:00
unix_timestamp = -3600 * int.Parse (timezone.Substring (1, 2));
process.StartInfo.FileName = "git";
process.StartInfo.Arguments = "log --format=\"%at\t%an\t%ae\t%H\" -" + count;
process.Start ();
process.WaitForExit ();
string output = process.StandardOutput.ReadToEnd ().Trim ();
output = output.TrimStart ("\n".ToCharArray ());
string [] lines = Regex.Split (output, "\n");
Array.Sort (lines);
Array.Reverse (lines);
foreach (string line in lines) {
string [] parts = Regex.Split (line, "\t");
2010-09-08 13:33:27 +00:00
int local_timestamp = unix_timestamp + int.Parse (parts [0]);
string user_name = parts [1];
string user_email = parts [2];
string hash = parts [3];
2010-09-08 13:33:27 +00:00
DateTime date_time = SparkleHelpers.UnixTimestampToDateTime (local_timestamp);
SparkleCommit commit = new SparkleCommit (user_name, user_email, date_time, hash);
// Find out what has changed in the commit.
// --name-status lists affected files with the modification type,
// -C detects renames
process.StartInfo.Arguments = "show " + hash + " --name-status -C";
process.Start ();
process.WaitForExit ();
output = process.StandardOutput.ReadToEnd ().Trim ();
output = output.TrimStart ("\n".ToCharArray ());
string [] file_lines = Regex.Split (output, "\n");
foreach (string file_line in file_lines) {
string file_path = "";
if (file_line.Length > 1)
file_path = file_line.Substring (2);
if (file_line.StartsWith ("M\t"))
commit.Edited.Add (file_path);
if (file_line.StartsWith ("A\t"))
commit.Added.Add (file_path);
if (file_line.StartsWith ("D\t"))
commit.Deleted.Add (file_path);
if (file_line.StartsWith ("R")) {
file_path = file_line.Substring (5);
string [] paths = Regex.Split (file_path, "\t");
commit.MovedFrom.Add (paths [0]);
commit.MovedTo.Add (paths [1]);
}
}
commits.Add (commit);
}
return commits;
}
// Creates a pretty commit message based on what has changed
private string FormatCommitMessage ()
{
2010-08-29 10:38:34 +00:00
Process.StartInfo.Arguments = "status --porcelain";
Process.Start ();
2010-08-29 10:38:34 +00:00
string output = Process.StandardOutput.ReadToEnd ().TrimEnd ();
string [] lines = Regex.Split (output, "\n");
string file_name;
string file_action;
string message = null;
foreach (string line in lines) {
if (line.StartsWith ("A")) {
file_action = "added";
file_name = line.Substring (3).Trim ("\"".ToCharArray ());
2010-08-29 10:38:34 +00:00
message = file_action + " " + file_name + "";
}
if (line.StartsWith ("M")) {
file_action = "edited";
file_name = line.Substring (3).Trim ("\"".ToCharArray ());
2010-08-29 10:38:34 +00:00
message = file_action + " " + file_name + "";
}
2010-08-29 10:38:34 +00:00
if (line.StartsWith ("D")) {
file_action = "deleted";
file_name = line.Substring (3).Trim ("\"".ToCharArray ());
2010-08-29 10:38:34 +00:00
message = file_action + " " + file_name + "";
}
2010-08-29 10:38:34 +00:00
if (line.StartsWith ("R")) {
file_action = "moved";
message = file_action + " " + line.Substring (3).Trim ("\"".ToCharArray ())
.Replace (" -> ", " to\n")
.Replace ("\"", "")
.Replace ("\"", "");
2010-08-29 10:38:34 +00:00
}
2010-08-29 10:38:34 +00:00
if (line.StartsWith ("C")) {
file_action = "copied";
file_name = line.Substring (3).Trim ("\"".ToCharArray ());
2010-08-29 10:38:34 +00:00
}
}
2010-08-29 10:38:34 +00:00
if (lines.Length > 1)
message += " and " + (lines.Length - 1) + " more";
return message;
}
}
}