Merge remote-tracking branch 'hbons/master' into gettext-cs

Conflicts:
	.gitignore
	.gitmodules
	SparkleLib/Git/SparkleFetcherGit.cs
	SparkleLib/Hg/SparkleRepoHg.cs
	SparkleLib/SparkleConfig.cs
	SparkleLib/SparkleFetcherBase.cs
	SparkleLib/SparkleListenerIrc.cs
	SparkleShare/SparkleBubblesController.cs
	SparkleShare/SparkleControllerBase.cs
	SparkleShare/SparkleStatusIcon.cs
This commit is contained in:
serras 2012-01-07 14:13:36 +01:00
commit f4c4a94000
226 changed files with 42969 additions and 2267 deletions

2
.gitignore vendored
View file

@ -27,6 +27,8 @@ aclocal.m4
autom4te.cache/
bin/
obj/
/bin/
SparkleShare/Mac/bin
install-sh
libtool
ltmain.sh

View file

@ -1,4 +1,4 @@
basedirs = build help SmartIrc4net SparkleLib data po
basedirs = build help SparkleLib data po
SUBDIRS = $(basedirs) $(GUISUBDIRS)
DIST_SUBDIRS = $(basedirs) SparkleShare

22
NEWS
View file

@ -1,3 +1,25 @@
0.6.0 for Linux and Mac (Sun Dec 25 2011):
Hylke:
- Several fixes for annoying bugs and crashes
- Fix freeze on quit on Mac
- Show project and history size in the event log
0.4.2 for Linux and Mac (Fri Dec 2 2011):
Hylke: Fix crash trying to add a project
0.4.1 for Linux and Mac (Tue Nov 29 2011):
Hylke: Just some small tweaks and fixes:
- Bundle git with the Mac application
- Warn about potential global gitignore files
- Remove SmartIrc4Net
- Build system fixes
- Code cleanups
0.4.0 for Linux and Mac (Sun Nov 12 2011):
Hylke: It has been a while since the last release. Since so many
things changed, and it being (softly) incompatible with 0.2, I decided

145
README
View file

@ -1,145 +0,0 @@
# SparkleShare
SparkleShare is a collaboration and sharing tool that is designed to keep
things simple and to stay out of your way. It allows you to instantly sync
with any Git repository you have access to.
SparkleShare currently works on Linux and Mac. A Windows port and mobile
device support are planned for the future.
# License
SparkleShare is free software and licensed under the GNU GPLv3 or later. You
are welcome to change and redistribute it under certain conditions. For more
information see the LICENSE file or visit http://www.gnu.org/licenses/gpl-3.0.html
# Run on Linux
Requirements:
- git >= 1.7.0
- gtk-sharp2
- gvfs
- intltool
- libnotify
- mono-core >= 2.8
- notify-sharp
- nautilus-python
- openssh
- pygtk
- webkitgtk
- webkit-sharp
Run the service, either click the SparkleShare launcher or:
$ sparkleshare start
You can stop the service via the graphical interface or by typing:
$ sparkleshare stop
For help:
$ sparkleshare --help
Note:
SparkleShare creates its own RSA keypair in ~/config/sparkleshare/ and uses
that for authentication. Please mind this if you're planning to set up your
own server by hand.
# Build on Linux
Installing the build dependencies on Debian or Ubuntu:
$ sudo apt-get install gtk-sharp2 mono-runtime mono-devel monodevelop \
libndesk-dbus1.0-cil-dev nant libnotify-cil-dev libgtk2.0-cil-dev mono-gmcs \
libwebkit-cil-dev intltool libtool python-nautilus libndesk-dbus-glib1.0-cil-dev
For Ubuntu libappindicator support, run the following before building:
$ sudo apt-get install libappindicator0.1-cil-dev
On Fedora:
$ sudo yum install gtk-sharp2-devel mono-core mono-devel monodevelop \
ndesk-dbus-devel ndesk-dbus-glib-devel nautilus-python-devel nant \
notify-sharp-devel webkit-sharp-devel webkitgtk-devel libtool intltool \
gnome-doc-utils
You can build and install SparkleShare like this:
$ ./configure --prefix=/usr (or ./autogen.sh if you build from the repository)
$ make
$ sudo make install
Note:
Use '--prefix=/usr' if you want the Nautilus extension to work.
# Run on Mac
Just double-click the SparkleShare bundle.
# Build on Mac
Install Xcode, the Mono Framework, MonoDevelop and the MonoMac plugin (you can find it in MonoDevelop => Add-in Manager).
You may need to adjust some environment variables to let the build environment tools find mono:
$ export PATH=/Library/Frameworks/Mono.framework/Versions/Current/bin:$PATH
$ export PKG_CONFIG=/Library/Frameworks/Mono.framework/Versions/Current/bin/pkg-config
$ export PKG_CONFIG_PATH=/Library/Frameworks/Mono.framework/Versions/Current/lib/pkgconfig
Install git, automake, and intltool using MacPorts:
$ sudo port install git-core automake intltool
Start the first part of the build:
$ ./autogen.sh --enable-gtkui=no
$ make
Now that you have compiled the libraries, open 'SparkleShare/Mac/SparkleShare.sln' in
MonoDevelop and start the build.
To create the SparkleShare.app, make sure the project is focused and select Project from the menu bar
and click "Create Mac Installer...". Make sure to select "Don't link assemblies".
Save the SparkleShare.app somewhere. Paste the contents of
the following file in SparkleShare.app/Contents/MonoBundle/config:
https://raw.github.com/gist/1aeffa61bac73fc08eca/0c0f09ef9e36864c35f34fd5e8bf4f99886be193/gistfile1.txt
Copy /Library/Frameworks/Mono.framework/Versions/Current/lib/libintl.dylib
to SparkleShare.app/Contents/Resources
Now you should have a working bundle that you can run.
# Info
Official website:
http://www.sparkleshare.org/
Source code:
http://github.com/SparkleShare/
IRC Channel:
#sparkleshare on irc.gnome.org
Wiki:
http://github.com/hbons/SparkleShare/wiki/
Report issues:
http://github.com/hbons/SparkleShare/issues/
Translation project:
http://www.transifex.net/projects/p/sparkleshare/
Now have fun and create cool things together! :)

162
README.md Normal file
View file

@ -0,0 +1,162 @@
# SparkleShare
SparkleShare is a collaboration and sharing tool that is designed to keep
things simple and to stay out of your way. It allows you to instantly sync
with any Git repository you have access to.
SparkleShare currently works on Linux and Mac. A Windows port and mobile
device support are planned for the future.
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/21770/SparkleShare-Sharing-work-made-easy)
## License
SparkleShare is free software and licensed under the GNU GPLv3 or later. You
are welcome to change and redistribute it under certain conditions. For more
information see the LICENSE file or visit http://www.gnu.org/licenses/gpl-3.0.html
## Run on Linux
Requirements:
- git >= 1.7.0
- gtk-sharp2
- gvfs
- intltool
- libnotify
- mono-core >= 2.8
- notify-sharp
- nautilus-python
- openssh
- pygtk
- webkitgtk
- webkit-sharp
Run the service, either click the SparkleShare launcher or:
```bash
$ sparkleshare start
```
You can stop the service via the graphical interface or by typing:
```bash
$ sparkleshare stop
```
For help:
```bash
$ sparkleshare --help
```
**Note:**
SparkleShare creates its own RSA keypair in `~/config/sparkleshare/` and uses
that for authentication. Please mind this if you're planning to set up your
own server by hand.
## Build on Linux
### Install build dependencies
#### Debian or Ubuntu (apt):
```bash
$ sudo apt-get install gtk-sharp2 mono-runtime mono-devel monodevelop \
libndesk-dbus1.0-cil-dev nant libnotify-cil-dev libgtk2.0-cil-dev mono-mcs mono-gmcs \
libwebkit-cil-dev intltool libtool python-nautilus libndesk-dbus-glib1.0-cil-dev
```
#### Fedora (yum):
```bash
$ sudo yum install gtk-sharp2-devel mono-core mono-devel monodevelop \
ndesk-dbus-devel ndesk-dbus-glib-devel nautilus-python-devel nant \
notify-sharp-devel webkit-sharp-devel webkitgtk-devel libtool intltool \
gnome-doc-utils
```
For Ubuntu `libappindicator` support, install the following package:
```bash
$ sudo apt-get install libappindicator0.1-cil-dev
```
You can then build and install SparkleShare like this:
```bash
$ ./configure --prefix=/usr (or ./autogen.sh if you build from the repository)
$ make
$ sudo make install
```
**Note:** Use `--prefix=/usr` if you want the Nautilus extension to work.
## Run on Mac
Just double-click the SparkleShare bundle.
## Build on Mac
Install <tt>Xcode</tt>, the <tt>Mono</tt> Framework, <tt>MonoDevelop</tt> and the <tt>MonoMac</tt> plugin
(you can find it in <tt>MonoDevelop</tt> => <tt>Add-in Manager</tt>).
You may need to adjust some environment variables to let the build environment tools find mono:
```bash
$ export PATH=/Library/Frameworks/Mono.framework/Versions/Current/bin:$PATH
$ export PKG_CONFIG=/Library/Frameworks/Mono.framework/Versions/Current/bin/pkg-config
$ export PKG_CONFIG_PATH=/Library/Frameworks/Mono.framework/Versions/Current/lib/pkgconfig
```
Install <tt>git</tt>, <tt>automake</tt>, and <tt>intltool</tt> using <tt>MacPorts</tt>:
```bash
$ sudo port install git-core automake intltool
```
Start the first part of the build:
```bash
$ ./autogen.sh --enable-gtkui=no
$ make
```
Now that you have compiled the libraries, open `SparkleShare/Mac/SparkleShare.sln` in
MonoDevelop and start the build.
To create the <tt>SparkleShare.app</tt>, make sure the project is focused and select Project from the menu bar
and click <tt>"Create Mac Installer..."</tt>. Make sure to select <tt>"Don't link assemblies"</tt>.
Save the <tt>SparkleShare.app</tt> somewhere. Paste the contents of
the following file in `SparkleShare.app/Contents/MonoBundle/config`:
```
https://raw.github.com/gist/1aeffa61bac73fc08eca/0c0f09ef9e36864c35f34fd5e8bf4f99886be193/gistfile1.txt
```
Copy `/Library/Frameworks/Mono.framework/Versions/Current/lib/libintl.dylib` to `SparkleShare.app/Contents/Resources`
Now you should have a working bundle that you can run.
## Info
|||
|-----------------------------------:|:--------------------------|
| **Official website**: | http://www.sparkleshare.org/ |
| **Source code**: | http://github.com/SparkleShare/ |
| **IRC Channel**: | #sparkleshare on irc.gnome.org |
| **Wiki**: | http://github.com/hbons/SparkleShare/wiki/ |
| **Report issues**: | http://github.com/hbons/SparkleShare/issues/ |
| **Translation project**: | http://www.transifex.net/projects/p/sparkleshare/ |
Now have fun and create cool things together! :)

@ -1 +0,0 @@
Subproject commit 036e2233aea9bd3b4a30f8c5136daff0187eb5ec

View file

@ -90,10 +90,13 @@ namespace SparkleLib {
this.git.StartInfo.RedirectStandardError = true;
this.git.Start ();
double percentage = 1.0;
Regex progress_regex = new Regex (@"([0-9]+)%", RegexOptions.Compiled);
DateTime last_change = DateTime.Now;
TimeSpan change_interval = new TimeSpan (0, 0, 0, 1);
while (!this.git.StandardError.EndOfStream) {
string line = this.git.StandardError.ReadLine ();
Match match = progress_regex.Match (line);
@ -107,7 +110,7 @@ namespace SparkleLib {
// the "Receiving objects" stage which we count as the last 80%
if (line.Contains ("|"))
// "Receiving objects" stage
number = (number / 100 * 75 + 20);
number = (number / 100 * 80 + 20);
else
// "Compressing objects" stage
number = (number / 100 * 20);
@ -115,20 +118,21 @@ namespace SparkleLib {
if (number >= percentage) {
percentage = number;
// FIXME: for some reason it doesn't go above 95%
base.OnProgressChanged (percentage);
if (DateTime.Compare (last_change, DateTime.Now.Subtract (change_interval)) < 0) {
base.OnProgressChanged (percentage);
last_change = DateTime.Now;
}
}
System.Threading.Thread.Sleep (100);
}
this.git.WaitForExit ();
SparkleHelpers.DebugInfo ("Git", "Exit code " + this.git.ExitCode.ToString ());
if (this.git.ExitCode != 0) {
return false;
} else {
InstallConfiguration ();
InstallExcludeRules ();
@ -137,9 +141,33 @@ namespace SparkleLib {
}
public override string [] Warnings {
get {
SparkleGit git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath,
"config --global core.excludesfile");
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 ().Trim ();
git.WaitForExit ();
if (string.IsNullOrEmpty (output)) {
return null;
} else {
return new string [] {
string.Format ("You seem to have configured a system gitignore file. " +
"This may interfere with SparkleShare.\n({0})", output)
};
}
}
}
public override void Stop ()
{
if (this.git != null) {
if (this.git != null && !this.git.HasExited) {
this.git.Kill ();
this.git.Dispose ();
}
@ -179,22 +207,11 @@ namespace SparkleLib {
// Add a .gitignore file to the repo
private void InstallExcludeRules ()
{
string exclude_rules_file_path = SparkleHelpers.CombineMore (
this.target_folder, ".git", "info", "exclude");
DirectoryInfo info = Directory.CreateDirectory (SparkleHelpers.CombineMore (
this.target_folder, ".git", "info"));
string directory = Path.GetDirectoryName(exclude_rules_file_path);
if (directory == null)
{
return;
}
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
TextWriter writer = new StreamWriter (exclude_rules_file_path);
string exlude_rules_file_path = Path.Combine (info.FullName, "exclude");
TextWriter writer = new StreamWriter (exlude_rules_file_path);
// gedit and emacs
writer.WriteLine ("*~");
@ -226,7 +243,7 @@ namespace SparkleLib {
writer.WriteLine ("Thumbs.db");
writer.WriteLine ("Desktop.ini");
// MS Office
// MS Office
writer.WriteLine ("~*.tmp");
writer.WriteLine ("~*.TMP");
writer.WriteLine ("*~*.tmp");
@ -257,6 +274,7 @@ namespace SparkleLib {
}
}
public class SparkleGit : Process {
public SparkleGit (string path, string args) : base ()

View file

@ -26,15 +26,8 @@ namespace SparkleLib {
public class SparkleRepoGit : SparkleRepoBase {
private string exlude_rules_file_path;
private string ExclusionBlock = "#Temporary Exclusions";
public SparkleRepoGit (string path, SparkleBackend backend) :
base (path, backend) {
// Set exclude file path
exlude_rules_file_path = SparkleHelpers.CombineMore (
LocalPath, ".git", "info", "exclude");
}
base (path, backend) { }
private string identifier = null;
@ -66,6 +59,34 @@ namespace SparkleLib {
}
public override List<string> ExcludePaths {
get {
List<string> rules = new List<string> ();
rules.Add (Path.DirectorySeparatorChar + ".git");
return rules;
}
}
public override double Size {
get {
return CalculateSize (
new DirectoryInfo (LocalPath)
);
}
}
public override double HistorySize {
get {
return CalculateSize (
new DirectoryInfo (Path.Combine (LocalPath, ".git"))
);
}
}
public override string [] UnsyncedFilePaths {
get {
List<string> file_paths = new List<string> ();
@ -144,16 +165,73 @@ namespace SparkleLib {
public override bool SyncUp ()
{
Add ();
if (AnyDifferences) {
Add ();
string message = FormatCommitMessage ();
Commit (message);
string message = FormatCommitMessage ();
Commit (message);
}
SparkleGit git = new SparkleGit (LocalPath, "push origin master");
SparkleGit git = new SparkleGit (LocalPath,
"push --progress " + // Redirects progress stats to standarderror
"origin master");
git.StartInfo.RedirectStandardError = true;
git.Start ();
git.StandardOutput.ReadToEnd ();
double percentage = 1.0;
Regex progress_regex = new Regex (@"([0-9]+)%", RegexOptions.Compiled);
DateTime last_change = DateTime.Now;
TimeSpan change_interval = new TimeSpan (0, 0, 0, 1);
while (!git.StandardError.EndOfStream) {
string line = git.StandardError.ReadLine ();
Match match = progress_regex.Match (line);
string speed = "";
double number = 0.0;
if (match.Success) {
number = double.Parse (match.Groups [1].Value);
// The pushing progress consists of two stages: the "Compressing
// objects" stage which we count as 20% of the total progress, and
// the "Writing objects" stage which we count as the last 80%
if (line.StartsWith ("Compressing")) {
// "Compressing objects" stage
number = (number / 100 * 20);
} else {
// "Writing objects" stage
number = (number / 100 * 80 + 20);
if (line.Contains ("|")) {
speed = line.Substring (line.IndexOf ("|") + 1).Trim ();
speed = speed.Replace (", done.", "").Trim ();
speed = speed.Replace ("i", "");
speed = speed.Replace ("KB/s", "ᴋʙ/s");
speed = speed.Replace ("MB/s", "ᴍʙ/s");
}
}
}
if (number >= percentage) {
percentage = number;
if (percentage == 100.0)
percentage = 99.0;
if (DateTime.Compare (last_change, DateTime.Now.Subtract (change_interval)) < 0) {
base.OnSyncProgressChanged (percentage, speed);
last_change = DateTime.Now;
}
}
}
git.WaitForExit ();
if (git.ExitCode == 0)
return true;
else
@ -163,10 +241,63 @@ namespace SparkleLib {
public override bool SyncDown ()
{
SparkleGit git = new SparkleGit (LocalPath, "fetch -v");
SparkleGit git = new SparkleGit (LocalPath, "fetch --progress");
git.StartInfo.RedirectStandardError = true;
git.Start ();
double percentage = 1.0;
Regex progress_regex = new Regex (@"([0-9]+)%", RegexOptions.Compiled);
DateTime last_change = DateTime.Now;
TimeSpan change_interval = new TimeSpan (0, 0, 0, 1);
while (!git.StandardError.EndOfStream) {
string line = git.StandardError.ReadLine ();
Match match = progress_regex.Match (line);
string speed = "";
double number = 0.0;
if (match.Success) {
number = double.Parse (match.Groups [1].Value);
// The fetching progress consists of two stages: the "Compressing
// objects" stage which we count as 20% of the total progress, and
// the "Receiving objects" stage which we count as the last 80%
if (line.StartsWith ("Compressing")) {
// "Compressing objects" stage
number = (number / 100 * 20);
} else {
// "Writing objects" stage
number = (number / 100 * 80 + 20);
if (line.Contains ("|")) {
speed = line.Substring (line.IndexOf ("|") + 1).Trim ();
speed = speed.Replace (", done.", "").Trim ();
speed = speed.Replace ("i", "");
speed = speed.Replace ("KB/s", "ᴋʙ/s");
speed = speed.Replace ("MB/s", "ᴍʙ/s");
}
}
}
if (number >= percentage) {
percentage = number;
if (percentage == 100.0)
percentage = 99.0;
if (DateTime.Compare (last_change, DateTime.Now.Subtract (change_interval)) < 0) {
base.OnSyncProgressChanged (percentage, speed);
last_change = DateTime.Now;
}
}
}
git.WaitForExit ();
if (git.ExitCode == 0) {
Rebase ();
return true;
@ -216,6 +347,7 @@ namespace SparkleLib {
if (value) {
if (!File.Exists (unsynced_file_path))
File.Create (unsynced_file_path).Close ();
} else {
File.Delete (unsynced_file_path);
}
@ -233,177 +365,16 @@ namespace SparkleLib {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged");
}
// Add a new file to be ignored
public override bool AddExclusionRule (FileSystemEventArgs args) {
string RelativePath = SparkleHelpers.DiffPaths(args.FullPath, LocalPath);
List<String> exclusions;
try {
exclusions = ReadExclusionRules();
}
catch {
return false;
}
// Look for the local exclusions section
bool added = false;
for(int i = 0; i < exclusions.Count; i++) {
string entry = exclusions[i];
if(entry.Equals(ExclusionBlock)) {
// add a new exclusion rule containing a file path
exclusions.Insert(i + 1, RelativePath);
added = true;
break;
}
}
/*
* For compability to existing repos:
* Add a "#Temporary Exclusions"-Block to the
* ignore file in order to recognize this
* exclude rules later on
*/
if(!added) {
exclusions.Add(ExclusionBlock);
exclusions.Add(RelativePath);
}
// Write exceptions list back to file
return WriteExclusionRules(exclusions);
}
// Check whether a specific rule exists in the exclusion file
public override bool ExclusionRuleExists(FileSystemEventArgs args) {
string RelativePath = SparkleHelpers.DiffPaths(args.FullPath, LocalPath);
List<String> exclusions;
try {
// Read rules from temporary block only
exclusions = ReadExclusionRules(true);
foreach(string entry in exclusions) {
if(entry.Equals(RelativePath)) {
return true;
}
}
} catch {
SparkleHelpers.DebugInfo("Error", "Cannot determine whether an exclusion rule for " +
args.FullPath + " already exists or not.");
return false;
}
return false;
}
// Remove file from exclusion list when they are readable again
public override bool RemoveExclusionRule(FileSystemEventArgs args) {
string RelativePath = SparkleHelpers.DiffPaths(args.FullPath, LocalPath);
List<String> exclusions;
try {
exclusions = ReadExclusionRules();
/*
* Removing a rule should only apply to rules in the "Temporary Exclusion"-block.
* Therefore we first read until reaching the block and then remove the rule.
*
* We cannot use ReadExclusionRules(true) here since we write all lines back
* to the file. This would result in a crippled exclusion file.
*/
bool BlockReached = false;
foreach(string entry in exclusions) {
if(entry.Equals(ExclusionBlock)) {
BlockReached = true;
}
// Remove this rule
if(BlockReached && entry.Equals(RelativePath)) {
exclusions.Remove(entry);
break;
}
}
return WriteExclusionRules(exclusions);
} catch {
SparkleHelpers.DebugInfo("Error", "Unable to remove exclusion rule for entry " + RelativePath);
return false;
}
}
// Reads the exclusion rules file into a string list
private List<String> ReadExclusionRules() {
List<String> exclusions = new List<String>();
TextReader reader = new StreamReader (exlude_rules_file_path);;
try {
while(reader.Peek() > -1) {
exclusions.Add(reader.ReadLine().TrimEnd());
}
}
catch (IOException e) {
SparkleHelpers.DebugInfo("Error", "Reading from exclusion file failed: " + e.Message);
return new List<String>();
}
finally {
if(reader != null) {
reader.Close();
}
}
return exclusions;
}
// Reads rules only from temporary exclusion block
private List<String> ReadExclusionRules(bool TempOnly) {
if(TempOnly) {
bool ForceRead = false;
List<String> exclusions = new List<String>();
foreach(string entry in ReadExclusionRules()) {
if(ForceRead || entry.Equals(ExclusionBlock)) {
exclusions.Add(entry);
ForceRead = true;
}
}
return exclusions;
}
return ReadExclusionRules();
}
// Writes the exclusion rules file with a given string list
private bool WriteExclusionRules(List<String> lines) {
TextWriter writer = new StreamWriter (exlude_rules_file_path);
try {
foreach(string line in lines) {
writer.WriteLine(line.TrimEnd());
}
} catch(IOException e) {
SparkleHelpers.DebugInfo("Error", "Writing into exclusion file failed: " + e.Message);
return false;
}
finally {
if(writer != null) {
writer.Close();
}
}
return true;
}
// Removes unneeded objects
private void CollectGarbage ()
/* private void CollectGarbage ()
{
SparkleGit git = new SparkleGit (LocalPath, "gc");
git.Start ();
git.WaitForExit ();
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Garbage collected.");
}
} */
// Commits the made changes
@ -486,11 +457,6 @@ namespace SparkleLib {
string output = git_status.StandardOutput.ReadToEnd ().TrimEnd ();
git_status.WaitForExit ();
if (String.IsNullOrEmpty (output)) {
// no conflict any more.
return;
}
string [] lines = output.Split ("\n".ToCharArray ());
foreach (string line in lines) {
@ -571,13 +537,10 @@ namespace SparkleLib {
List <SparkleChangeSet> change_sets = new List <SparkleChangeSet> ();
SparkleGit git_log = new SparkleGit (LocalPath, "log -" + count + " --raw -M --date=iso");
if ((SparkleBackend.Platform == PlatformID.Unix ||
SparkleBackend.Platform == PlatformID.MacOSX)) {
// this causes an IOException on windows
Console.OutputEncoding = System.Text.Encoding.Unicode;
}
// Console.InputEncoding = System.Text.Encoding.Unicode;
Console.OutputEncoding = System.Text.Encoding.Unicode;
SparkleGit git_log = new SparkleGit (LocalPath, "log -" + count + " --raw -M --date=iso");
git_log.Start ();
// Reading the standard output HAS to go before
@ -709,8 +672,12 @@ namespace SparkleLib {
FillEmptyDirectories (child_path);
}
if (Directory.GetFiles (path).Length == 0 && !path.Equals (LocalPath))
if (Directory.GetFiles (path).Length == 0 &&
Directory.GetDirectories (path).Length == 0 &&
!path.Equals (LocalPath)) {
File.Create (Path.Combine (path, ".empty")).Close ();
}
}
@ -752,6 +719,10 @@ namespace SparkleLib {
foreach (string added in Added) {
file_name = added.Trim ("\"".ToCharArray ());
if (file_name.EndsWith (".empty"))
file_name = file_name.Substring (0, file_name.Length - 6);
message += "+ " + file_name + "" + n;
count++;
@ -761,6 +732,10 @@ namespace SparkleLib {
foreach (string modified in Modified) {
file_name = modified.Trim ("\"".ToCharArray ());
if (file_name.EndsWith (".empty"))
file_name = file_name.Substring (0, file_name.Length - 6);
message += "/ " + file_name + "" + n;
count++;
@ -770,6 +745,10 @@ namespace SparkleLib {
foreach (string removed in Removed) {
file_name = removed.Trim ("\"".ToCharArray ());
if (file_name.EndsWith (".empty"))
file_name = file_name.Substring (0, file_name.Length - 6);
message += "- " + file_name + "" + n;
count++;
@ -796,5 +775,38 @@ namespace SparkleLib {
base.CreateInitialChangeSet ();
SyncUp ();
}
// Recursively gets a folder's size in bytes
public override double CalculateSize (DirectoryInfo parent)
{
if (!Directory.Exists (parent.ToString ()))
return 0;
double size = 0;
// Ignore the temporary 'rebase-apply' and '.tmp' directories. This prevents potential
// crashes when files are being queried whilst the files have already been deleted.
if (parent.Name.Equals ("rebase-apply") ||
parent.Name.Equals (".tmp"))
return 0;
try {
foreach (FileInfo file in parent.GetFiles()) {
if (!file.Exists)
return 0;
size += file.Length;
}
foreach (DirectoryInfo directory in parent.GetDirectories ())
size += CalculateSize (directory);
} catch (Exception) {
return 0;
}
return size;
}
}
}

View file

@ -1,172 +0,0 @@
// SparkleShare, a collaboration and sharing tool.
// 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
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.IO;
using System.Diagnostics;
using System.Xml;
namespace SparkleLib {
// Sets up a fetcher that can get remote folders
public class SparkleFetcherHg : SparkleFetcherBase {
public SparkleFetcherHg (string server, string remote_folder, string target_folder) :
base (server, remote_folder, target_folder) { }
public override bool Fetch ()
{
SparkleHg hg = new SparkleHg (SparklePaths.SparkleTmpPath,
"clone \"" + base.remote_url + "\" " + "\"" + base.target_folder + "\"");
hg.Start ();
hg.WaitForExit ();
SparkleHelpers.DebugInfo ("Hg", "Exit code " + hg.ExitCode.ToString ());
if (hg.ExitCode != 0) {
return false;
} else {
InstallConfiguration ();
InstallExcludeRules ();
return true;
}
}
// Install the user's name and email and some config into
// the newly cloned repository
private void InstallConfiguration ()
{
string global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config.xml");
if (!File.Exists (global_config_file_path))
return;
string repo_config_file_path = SparkleHelpers.CombineMore (base.target_folder, ".hg", "hgrc");
string config = String.Join (Environment.NewLine, File.ReadAllLines (repo_config_file_path));
// Add user info
string n = Environment.NewLine;
XmlDocument xml = new XmlDocument();
xml.Load (global_config_file_path);
XmlNode node_name = xml.SelectSingleNode ("//user/name/text()");
XmlNode node_email = xml.SelectSingleNode ("//user/email/text()");
// TODO this ignore duplicate names (FolderName (2))
string ignore_file_path = base.target_folder.Replace (SparklePaths.SparkleTmpPath,
SparklePaths.SparklePath);
ignore_file_path = SparkleHelpers.CombineMore (ignore_file_path, ".hg", "hgignore");
config += n +
"[ui]" + n +
"username = " + node_name.Value + " <" + node_email.Value + ">" + n +
"ignore = " + ignore_file_path + n;
// Write the config to the file
TextWriter writer = new StreamWriter (repo_config_file_path);
writer.WriteLine (config);
writer.Close ();
string style_file_path = SparkleHelpers.CombineMore (base.target_folder, ".hg", "log.style");
string style = "changeset = \"{file_mods}{file_adds}{file_dels}\"" + n +
"file_add = \"A {file_add}\\n\"" + n +
"file_mod = \"M {file_mod}\\n\"" + n +
"file_del = \"D {file_del}\\n\"" + n;
writer = new StreamWriter (style_file_path);
writer.WriteLine (style);
writer.Close ();
SparkleHelpers.DebugInfo ("Config", "Added configuration to '" + repo_config_file_path + "'");
}
// Add a .gitignore file to the repo
private void InstallExcludeRules ()
{
string exlude_rules_file_path = SparkleHelpers.CombineMore (
this.target_folder, ".hg", "hgignore");
TextWriter writer = new StreamWriter (exlude_rules_file_path);
writer.WriteLine ("syntax: glob");
// gedit and emacs
writer.WriteLine ("*~");
// vi(m)
writer.WriteLine (".*.sw[a-z]");
writer.WriteLine ("*.un~");
writer.WriteLine ("*.swp");
writer.WriteLine ("*.swo");
// KDE
writer.WriteLine (".directory");
// Mac OSX
writer.WriteLine (".DS_Store");
writer.WriteLine ("Icon?");
writer.WriteLine ("._*");
writer.WriteLine (".Spotlight-V100");
writer.WriteLine (".Trashes");
// Mac OSX
writer.WriteLine ("*(Autosaved).graffle");
// Windows
writer.WriteLine ("Thumbs.db");
writer.WriteLine ("Desktop.ini");
// CVS
writer.WriteLine ("*/CVS/*");
writer.WriteLine (".cvsignore");
writer.WriteLine ("*/.cvsignore");
// Subversion
writer.WriteLine ("/.svn/*");
writer.WriteLine ("*/.svn/*");
writer.Close ();
}
}
public class SparkleHg : Process {
public SparkleHg (string path, string args) : base ()
{
EnableRaisingEvents = true;
StartInfo.FileName = "/opt/local/bin/hg";
StartInfo.Arguments = args;
StartInfo.RedirectStandardOutput = true;
StartInfo.UseShellExecute = false;
StartInfo.WorkingDirectory = path;
}
new public void Start ()
{
SparkleHelpers.DebugInfo ("Cmd", StartInfo.FileName + " " + StartInfo.Arguments);
base.Start ();
}
}
}

View file

@ -1,487 +0,0 @@
// SparkleShare, a collaboration and sharing tool.
// 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
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
namespace SparkleLib {
public class SparkleRepoHg : SparkleRepoBase {
private string exlude_rules_file_path;
private string ExclusionBlock = "#Temporary Exclusions";
public SparkleRepoHg (string path, SparkleBackend backend) :
base (path, backend) {
// Set exclude file path
exlude_rules_file_path = SparkleHelpers.CombineMore (
LocalPath, ".hg", "hgignore");
}
public override string Identifier {
get {
SparkleHg hg = new SparkleHg (LocalPath, "log -r : --limit 1 --template \"{node}\"");
hg.Start ();
hg.WaitForExit ();
return hg.StandardOutput.ReadToEnd ();
}
}
public override string CurrentRevision {
get {
SparkleHg hg = new SparkleHg (LocalPath, "log --limit 1 --template \"{node}\"");
hg.Start ();
hg.WaitForExit ();
string hash = hg.StandardOutput.ReadToEnd ().Trim ();
if (hash.Length > 0)
return hash;
else
return null;
}
}
public override bool CheckForRemoteChanges ()
{
return true; // Mercurial doesn't have a way to check for the remote hash
}
public override bool SyncUp ()
{
Add ();
string message = FormatCommitMessage ();
Commit (message);
SparkleHg hg = new SparkleHg (LocalPath, "push");
hg.Start ();
hg.WaitForExit ();
if (hg.ExitCode == 0) {
return true;
} else {
return false;
}
}
public override bool SyncDown ()
{
SparkleHg hg = new SparkleHg (LocalPath, "pull");
hg.Start ();
hg.WaitForExit ();
if (hg.ExitCode == 0) {
Merge ();
return true;
} else {
return false;
}
}
public override bool AnyDifferences {
get {
SparkleHg hg = new SparkleHg (LocalPath, "status");
hg.Start ();
hg.WaitForExit ();
string output = hg.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;
}
}
public override bool HasUnsyncedChanges {
get {
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath,
".hg", "has_unsynced_changes");
return File.Exists (unsynced_file_path);
}
set {
string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath,
".hg", "has_unsynced_changes");
if (value) {
if (!File.Exists (unsynced_file_path))
File.Create (unsynced_file_path).Close ();
} else {
File.Delete (unsynced_file_path);
}
}
}
// Stages the made changes
private void Add ()
{
SparkleHg hg = new SparkleHg (LocalPath, "addremove --quiet");
hg.Start ();
hg.WaitForExit ();
SparkleHelpers.DebugInfo ("Hg", "[" + Name + "] Changes staged");
}
// Add a new file to be ignored
public override bool AddExclusionRule (FileSystemEventArgs args) {
string RelativePath = SparkleHelpers.DiffPaths(args.FullPath, LocalPath);
List<String> exclusions;
try {
exclusions = ReadExclusionRules();
}
catch {
return false;
}
// Look for the local exclusions section
bool added = false;
for(int i = 0; i < exclusions.Count; i++) {
string entry = exclusions[i];
if(entry.Equals(ExclusionBlock)) {
// add a new exclusion rule containing a file path
exclusions.Insert(i + 1, RelativePath);
added = true;
break;
}
}
/*
* For compability to existing repos:
* Add a "#Temporary Exclusions"-Block to the
* ignore file in order to recognize this
* exclude rules later on
*/
if(!added) {
exclusions.Add(ExclusionBlock);
exclusions.Add(RelativePath);
}
// Write exceptions list back to file
return WriteExclusionRules(exclusions);
}
// Check whether a specific rule exists in the exclusion file
public override bool ExclusionRuleExists(FileSystemEventArgs args) {
string RelativePath = SparkleHelpers.DiffPaths(args.FullPath, LocalPath);
List<String> exclusions;
try {
// Read rules from temporary block only
exclusions = ReadExclusionRules(true);
foreach(string entry in exclusions) {
if(entry.Equals(RelativePath)) {
return true;
}
}
} catch {
SparkleHelpers.DebugInfo("Error", "Cannot determine whether an exclusion rule for " +
args.FullPath + " already exists or not.");
return false;
}
return false;
}
// Remove file from exclusion list when they are readable again
public override bool RemoveExclusionRule(FileSystemEventArgs args) {
string RelativePath = SparkleHelpers.DiffPaths(args.FullPath, LocalPath);
List<String> exclusions;
try {
exclusions = ReadExclusionRules();
/*
* Removing a rule should only apply to rules in the "Temporary Exclusion"-block.
* Therefore we first read until reaching the block and then remove the rule.
*
* We cannot use ReadExclusionRules(true) here since we write all lines back
* to the file. This would result in a crippled exclusion file.
*/
bool BlockReached = false;
foreach(string entry in exclusions) {
if(entry.Equals(ExclusionBlock)) {
BlockReached = true;
}
// Remove this rule
if(BlockReached && entry.Equals(RelativePath)) {
exclusions.Remove(entry);
break;
}
}
return WriteExclusionRules(exclusions);
} catch {
SparkleHelpers.DebugInfo("Error", "Unable to remove exclusion rule for entry " + RelativePath);
return false;
}
}
// Reads the exclusion rules file into a string list
private List<String> ReadExclusionRules() {
List<String> exclusions = new List<String>();
TextReader reader = new StreamReader (exlude_rules_file_path);;
try {
while(reader.Peek() > -1) {
exclusions.Add(reader.ReadLine().TrimEnd());
}
}
catch (IOException e) {
SparkleHelpers.DebugInfo("Error", "Reading from exclusion file failed: " + e.Message);
return new List<String>();
}
finally {
if(reader != null) {
reader.Close();
}
}
return exclusions;
}
// Reads rules only from temporary exclusion block
private List<String> ReadExclusionRules(bool TempOnly) {
if(TempOnly) {
bool ForceRead = false;
List<String> exclusions = new List<String>();
foreach(string entry in ReadExclusionRules()) {
if(ForceRead || entry.Equals(ExclusionBlock)) {
exclusions.Add(entry);
ForceRead = true;
}
}
return exclusions;
}
return ReadExclusionRules();
}
// Writes the exclusion rules file with a given string list
private bool WriteExclusionRules(List<String> lines) {
TextWriter writer = new StreamWriter (exlude_rules_file_path);
try {
foreach(string line in lines) {
writer.WriteLine(line.TrimEnd());
}
} catch(IOException e) {
SparkleHelpers.DebugInfo("Error", "Writing into exclusion file failed: " + e.Message);
return false;
}
finally {
if(writer != null) {
writer.Close();
}
}
return true;
}
// Commits the made changes
private void Commit (string message)
{
if (!AnyDifferences)
return;
SparkleHg hg = new SparkleHg (LocalPath, "commit -m '" + message + "'");
hg.Start ();
hg.WaitForExit ();
SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message);
}
// Merges the fetched changes
private void Merge ()
{
DisableWatching ();
if (AnyDifferences) {
Add ();
string commit_message = FormatCommitMessage ();
Commit (commit_message);
}
SparkleHg hg = new SparkleHg (LocalPath, "update");
hg.Start ();
hg.WaitForExit ();
EnableWatching ();
}
// Returns a list of the latest change sets
public override List<SparkleChangeSet> GetChangeSets (int count)
{
if (count < 1)
count = 30;
List <SparkleChangeSet> change_sets = new List <SparkleChangeSet> ();
SparkleHg hg_log = new SparkleHg (LocalPath, "log --limit " + count + " --style changelog --verbose --stat");
Console.OutputEncoding = System.Text.Encoding.Unicode;
hg_log.Start ();
// Reading the standard output HAS to go before
// WaitForExit, or it will hang forever on output > 4096 bytes
string output = hg_log.StandardOutput.ReadToEnd ();
hg_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 ("2") && line.EndsWith (")") && j > 0) {
entries.Add (entry);
entry = "";
}
entry += line + "\n";
j++;
last_entry = entry;
}
entries.Add (last_entry);
Regex regex = new Regex (@"([0-9]{4})-([0-9]{2})-([0-9]{2}).*([0-9]{2}):([0-9]{2}).*.([0-9]{4})" +
"(.+)<(.+)>.*.([a-z0-9]{12})", RegexOptions.Compiled);
foreach (string log_entry in entries) {
bool is_merge_commit = false;
Match match = regex.Match (log_entry);
if (!match.Success)
continue;
SparkleChangeSet change_set = new SparkleChangeSet () {
Revision = match.Groups [9].Value,
IsMagical = is_merge_commit
};
change_set.User.Name = match.Groups [7].Value.Trim ();
change_set.User.Email = match.Groups [8].Value;
change_set.Timestamp = new DateTime (int.Parse (match.Groups [1].Value),
int.Parse (match.Groups [2].Value), int.Parse (match.Groups [3].Value),
int.Parse (match.Groups [4].Value), int.Parse (match.Groups [5].Value), 0);
string [] entry_lines = log_entry.Split ("\n".ToCharArray ());
foreach (string entry_line in entry_lines) {
if (!entry_line.StartsWith ("\t* "))
continue;
if (entry_line.EndsWith ("new file.")) {
string files = entry_line.Substring (3, entry_line.Length - 13);
string [] added_files = files.Split (",".ToCharArray ());
foreach (string added_file in added_files) {
string file = added_file.TrimEnd (": ".ToCharArray ());
change_set.Added.Add (file);
}
} else if (entry_line.EndsWith ("deleted file.")) {
string files = entry_line.Substring (3, entry_line.Length - 17);
string [] deleted_files = files.Split (",".ToCharArray ());
foreach (string deleted_file in deleted_files) {
string file = deleted_file.TrimEnd (": ".ToCharArray ());
change_set.Deleted.Add (file);
}
} else if (!"".Equals (entry_line.Trim ())){
string files = entry_line.Substring (3);
files = files.TrimEnd (":".ToCharArray());
string [] edited_files = files.Split (",".ToCharArray ());
foreach (string edited_file in edited_files) {
if (!change_set.Added.Contains (edited_file) &&
!change_set.Deleted.Contains (edited_file)) {
change_set.Edited.Add (edited_file);
}
}
}
}
change_sets.Add (change_set);
}
return change_sets;
}
// Creates a pretty commit message based on what has changed
private string FormatCommitMessage () // TODO
{
return "SparkleShare Hg";
}
public override void CreateInitialChangeSet ()
{
base.CreateInitialChangeSet ();
Add ();
string message = FormatCommitMessage ();
Commit (message);
}
public override bool UsesNotificationCenter
{
get {
string file_path = SparkleHelpers.CombineMore (LocalPath, ".hg", "disable_notification_center");
return !File.Exists (file_path);
}
}
}
}

View file

@ -1,12 +1,6 @@
ASSEMBLY = SparkleLib
TARGET = library
if HAVE_SMARTIRC4NET
LINK = $(REF_SPARKLELIB) $(LINK_SMARTIRC4NET_SYSTEM)
else
LINK = $(REF_SPARKLELIB) $(LINK_SMARTIRC4NET_LOCAL)
endif
SOURCES = \
Defines.cs \
Git/SparkleFetcherGit.cs \
@ -17,17 +11,11 @@ SOURCES = \
SparkleFetcherBase.cs \
SparkleHelpers.cs \
SparkleListenerBase.cs \
SparkleListenerIrc.cs \
SparkleListenerTcp.cs \
SparkleOptions.cs \
SparkleRepoBase.cs \
SparkleWatcher.cs
SMARTIRC4NET_FILES_EXPANDED = $(foreach file, $(SMARTIRC4NET_FILES), $(top_builddir)/$(file))
EXTRA_BUNDLE = $(SMARTIRC4NET_FILES_EXPANDED)
install-data-hook:
for ASM in $(EXTRA_BUNDLE); do \
$(INSTALL) -m 0755 $$ASM $(DESTDIR)$(moduledir); \

View file

@ -102,7 +102,7 @@ namespace SparkleLib {
public class SparkleFolder {
public string Name;
// TODO: Uri
public Uri RemoteAddress;
public string FullPath {
get {

View file

@ -19,10 +19,7 @@ using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;
#if __MonoCS__
using Mono.Unix;
#endif
using System.Security.Principal;
namespace SparkleLib {
@ -33,12 +30,14 @@ namespace SparkleLib {
"sparkleshare");
public static SparkleConfig DefaultConfig = new SparkleConfig (ConfigPath, "config.xml");
public string FullPath;
public string HomePath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
public string TmpPath;
public string HomePath {
get {
return Environment.GetFolderPath (Environment.SpecialFolder.Personal);
}
}
public string FoldersPath {
get {
@ -49,11 +48,16 @@ namespace SparkleLib {
}
}
public string TmpPath {
get {
return Path.Combine (FoldersPath, ".tmp");
}
}
public SparkleConfig (string config_path, string config_file_name)
{
FullPath = System.IO.Path.Combine (config_path, config_file_name);
TmpPath = Path.Combine (FoldersPath, ".tmp");
if (!Directory.Exists (config_path)) {
Directory.CreateDirectory (config_path);
@ -99,13 +103,13 @@ namespace SparkleLib {
if (SparkleBackend.Platform == PlatformID.Unix ||
SparkleBackend.Platform == PlatformID.MacOSX) {
#if __MonoCS__
user_name = new UnixUserInfo (UnixEnvironment.UserName).RealName;
user_name = Environment.UserName;
if (string.IsNullOrEmpty (user_name))
user_name = UnixEnvironment.UserName;
user_name = "";
else
user_name = user_name.TrimEnd (",".ToCharArray());
#endif
user_name = user_name.TrimEnd (",".ToCharArray ());
} else {
user_name = Environment.UserName;
}
@ -148,10 +152,59 @@ namespace SparkleLib {
email_node.InnerText = user.Email;
this.Save ();
ConfigureSSH ();
}
}
private void ConfigureSSH ()
{
if (User.Email.Equals ("Unknown"))
return;
string path = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
if (!(SparkleBackend.Platform == PlatformID.Unix ||
SparkleBackend.Platform == PlatformID.MacOSX)) {
path = Environment.ExpandEnvironmentVariables ("%HOMEDRIVE%%HOMEPATH%");
}
string ssh_config_path = Path.Combine (path, ".ssh");
string ssh_config_file_path = SparkleHelpers.CombineMore (path, ".ssh", "config");
string ssh_config = "IdentityFile " +
Path.Combine (SparkleConfig.ConfigPath, "sparkleshare." + User.Email + ".key");
if (!Directory.Exists (ssh_config_path))
Directory.CreateDirectory (ssh_config_path);
if (File.Exists (ssh_config_file_path)) {
string current_config = File.ReadAllText (ssh_config_file_path);
if (current_config.Contains (ssh_config))
return;
if (current_config.EndsWith ("\n\n"))
ssh_config = "# SparkleShare's key\n" + ssh_config;
else if (current_config.EndsWith ("\n"))
ssh_config = "\n# SparkleShare's key\n" + ssh_config;
else
ssh_config = "\n\n# SparkleShare's key\n" + ssh_config;
TextWriter writer = File.AppendText (ssh_config_file_path);
writer.Write (ssh_config + "\n");
writer.Close ();
} else {
File.WriteAllText (ssh_config_file_path, ssh_config);
}
Chmod644 (ssh_config_file_path);
SparkleHelpers.DebugInfo ("Config", "Added key to " + ssh_config_file_path);
}
public List<string> Folders {
get {
List<string> folders = new List<string> ();
@ -348,6 +401,16 @@ namespace SparkleLib {
this.Save (FullPath);
SparkleHelpers.DebugInfo ("Config", "Updated \"" + FullPath + "\"");
}
private void Chmod644 (string file_path)
{
// Hack to be able to set the permissions on a file
// that OpenSSH still likes without resorting to Mono.Unix
FileInfo file_info = new FileInfo (file_path);
file_info.Attributes = FileAttributes.ReadOnly;
file_info.Attributes = FileAttributes.Normal;
}
}

View file

@ -18,20 +18,17 @@
using System;
using System.IO;
using System.Diagnostics;
using System.Security.AccessControl;
using System.Text.RegularExpressions;
using System.Threading;
#if __MonoCS__
using Mono.Unix;
#endif
namespace SparkleLib {
// Sets up a fetcher that can get remote folders
public abstract class SparkleFetcherBase {
public delegate void StartedEventHandler ();
public delegate void FinishedEventHandler ();
public delegate void FinishedEventHandler (string [] warnings);
public delegate void FailedEventHandler ();
public delegate void ProgressChangedEventHandler (double percentage);
@ -54,7 +51,7 @@ namespace SparkleLib {
public abstract bool Fetch ();
public abstract string [] Warnings { get; }
// Clones the remote repository
public void Start ()
@ -85,7 +82,7 @@ namespace SparkleLib {
EnableHostKeyCheckingForHost (host);
if (Finished != null)
Finished ();
Finished (Warnings);
} else {
SparkleHelpers.DebugInfo ("Fetcher", "Failed");
@ -159,13 +156,8 @@ namespace SparkleLib {
File.WriteAllText (ssh_config_file_path, ssh_config);
}
#if __MonoCS__
UnixFileSystemInfo file_info = new UnixFileInfo (ssh_config_file_path);
file_info.FileAccessPermissions = (FileAccessPermissions.UserRead |
FileAccessPermissions.UserWrite);
#endif
SparkleHelpers.DebugInfo ("Fetcher", "Disabled host key checking " + host);
Chmod644 (ssh_config_file_path);
SparkleHelpers.DebugInfo ("Fetcher", "Disabled host key checking for " + host);
}
@ -173,8 +165,8 @@ namespace SparkleLib {
{
string path = SparkleConfig.DefaultConfig.HomePath;
if (!(SparkleBackend.Platform == PlatformID.Unix ||
SparkleBackend.Platform == PlatformID.MacOSX)) {
if (SparkleBackend.Platform != PlatformID.Unix &&
SparkleBackend.Platform != PlatformID.MacOSX) {
path = Environment.ExpandEnvironmentVariables ("%HOMEDRIVE%%HOMEPATH%");
}
@ -211,12 +203,7 @@ namespace SparkleLib {
} else {
File.WriteAllText (ssh_config_file_path, new_ssh_config.Trim ());
#if __MonoCS__
UnixFileSystemInfo file_info = new UnixFileInfo (ssh_config_file_path);
file_info.FileAccessPermissions = (FileAccessPermissions.UserRead |
FileAccessPermissions.UserWrite);
#endif
Chmod644 (ssh_config_file_path);
}
}
@ -234,5 +221,15 @@ namespace SparkleLib {
else
return null;
}
private void Chmod644 (string file_path)
{
// Hack to be able to set the permissions on a file
// that OpenSSH still likes without resorting to Mono.Unix
FileInfo file_info = new FileInfo (file_path);
file_info.Attributes = FileAttributes.ReadOnly;
file_info.Attributes = FileAttributes.Normal;
}
}
}

View file

@ -31,10 +31,6 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="Mono.Posix" />
<Reference Include="Meebey.SmartIrc4net, Version=0.4.5.0, Culture=neutral, PublicKeyToken=7868485fbf407e0f">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\bin\Meebey.SmartIrc4net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="SparkleRepoBase.cs" />

View file

@ -75,9 +75,6 @@ namespace SparkleLib {
case "tcp":
listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier));
break;
case "irc":
listeners.Add (new SparkleListenerIrc (announce_uri, folder_identifier));
break;
default:
listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier));
break;

View file

@ -1,221 +0,0 @@
// SparkleShare, a collaboration and sharing tool.
// 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
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using Meebey.SmartIrc4net;
namespace SparkleLib {
public class SparkleListenerIrc : SparkleListenerBase {
private Thread thread;
private IrcClient client;
private string nick;
private string announcements_password;
private bool allow_passwordless_join;
public SparkleListenerIrc (Uri server, string folder_identifier) :
base (server, folder_identifier)
{
// Try to get a uniqueish nickname
this.nick = SHA1 (DateTime.Now.ToString ("ffffff") + "sparkles");
// Most irc servers don't allow nicknames starting
// with a number, so prefix an alphabetic character
this.nick = "s" + this.nick.Substring (0, 7);
// Optional password to make the channel access more safe
this.announcements_password =
SparkleConfig.DefaultConfig.GetConfigOption ("announcements_password");
// Option to allow access to channel when no password is defined
try {
string option = SparkleConfig.DefaultConfig.GetConfigOption ("allow_passwordless_join");
this.allow_passwordless_join = (option == null || Convert.ToBoolean (option));
} catch (Exception) {
this.allow_passwordless_join = true;
}
base.channels.Add ("#" + folder_identifier);
this.client = new IrcClient () {
PingTimeout = 180,
PingInterval = 60
};
string proxy = Environment.GetEnvironmentVariable ("http_proxy");
Uri proxy_uri = null;
if (!String.IsNullOrEmpty (proxy) &&
Uri.TryCreate (proxy, UriKind.Absolute, out proxy_uri)) {
#if __MonoCS__
if (proxy_uri.Scheme == "http") {
this.client.ProxyType = ProxyType.Http;
this.client.ProxyHost = proxy_uri.Host;
this.client.ProxyPort = proxy_uri.Port;
}
#endif
}
this.client.OnConnected += delegate {
base.is_connecting = false;
OnConnected ();
};
this.client.OnDisconnected += delegate {
base.is_connecting = false;
OnDisconnected ();
};
this.client.OnError += delegate {
base.is_connecting = false;
OnDisconnected ();
};
this.client.OnChannelMessage += delegate (object o, IrcEventArgs args) {
string message = args.Data.Message.Trim ();
string folder_id = args.Data.Channel.Substring (1); // remove the starting hash
OnAnnouncement (new SparkleAnnouncement (folder_id, message));
};
}
public override bool IsConnected {
get {
return this.client.IsConnected;
}
}
// Starts a new thread and listens to the channel
public override void Connect ()
{
SparkleHelpers.DebugInfo ("ListenerIrc", "Connecting to " + Server);
base.is_connecting = true;
this.thread = new Thread (
new ThreadStart (delegate {
try {
// Connect, login, and join the channel
int port = base.server.Port;
if (port < 0)
port = 6667;
this.client.Connect (base.server.Host, port);
this.client.Login (this.nick, this.nick, 8, this.nick);
foreach (string channel in base.channels) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Joining channel " + channel);
if (!string.IsNullOrEmpty (this.announcements_password)) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Password set to access the channel");
this.client.RfcJoin (channel, this.announcements_password);
this.client.RfcMode (channel, "+k " + this.announcements_password);
} else {
if (this.allow_passwordless_join) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Accessing unprotected channel, change the setting to not access");
this.client.RfcJoin (channel);
} else {
SparkleHelpers.DebugInfo ("ListenerIrc", "Unprotected channel, change the setting to access");
base.is_connecting = false;
OnDisconnected ();
throw new ConnectionException ("Unprotected channel, change the setting to access");
}
}
this.client.RfcMode (channel, "+s");
}
// List to the channel, this blocks the thread
this.client.Listen ();
// Disconnect when we time out
this.client.Disconnect ();
} catch (ConnectionException e) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Could not connect to " + Server + ": " + e.Message);
}
})
);
this.thread.Start ();
}
public override void AlsoListenTo (string folder_identifier)
{
string channel = "#" + folder_identifier;
if (!base.channels.Contains (channel)) {
base.channels.Add (channel);
if (IsConnected) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Joining channel " + channel);
if (this.announcements_password != null) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Password set to access the channel");
this.client.RfcJoin (channel, this.announcements_password);
this.client.RfcMode (channel, "+k " + this.announcements_password);
} else {
if (allow_passwordless_join) {
SparkleHelpers.DebugInfo ("ListenerIrc", "Accessing a dangerous channel change the setting to not access");
this.client.RfcJoin (channel);
} else {
SparkleHelpers.DebugInfo ("ListenerIrc", "Dangerous channel, change the setting to access");
}
}
this.client.RfcMode (channel, "+s");
}
}
}
public override void Announce (SparkleAnnouncement announcement)
{
string channel = "#" + announcement.FolderIdentifier;
this.client.SendMessage (SendType.Message, channel, announcement.Message);
// Also announce to ourselves for debugging purposes
// base.OnAnnouncement (announcement);
}
public override void Dispose ()
{
this.thread.Abort ();
this.thread.Join ();
base.Dispose ();
}
// Creates a SHA-1 hash of input
private string SHA1 (string s)
{
SHA1 sha1 = new SHA1CryptoServiceProvider ();
Byte[] bytes = ASCIIEncoding.Default.GetBytes (s);
Byte[] encoded_bytes = sha1.ComputeHash (bytes);
return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", "");
}
}
}

View file

@ -1,37 +0,0 @@
// SparkleShare, a collaboration and sharing tool.
// 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
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.IO;
namespace SparkleLib {
public static class SparklePaths {
public static string HomePath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
public static string SparklePath = Path.Combine (HomePath ,"SparkleShare");
public static string SparkleTmpPath = Path.Combine (SparklePath, ".tmp");
public static string SparkleConfigPath = Path.Combine (Environment.GetFolderPath (
Environment.SpecialFolder.ApplicationData), "sparkleshare");
public static string SparkleLocalIconPath = Path.Combine (SparkleConfigPath, "icons");
public static string SparkleInstallPath = Path.Combine (Defines.PREFIX, "sparkleshare");
public static string SparkleIconPath = SparkleHelpers.CombineMore (Defines.DATAROOTDIR, "sparkleshare", "icons");
}
}

View file

@ -44,11 +44,13 @@ namespace SparkleLib {
private TimeSpan poll_interval;
private System.Timers.Timer local_timer = new System.Timers.Timer () { Interval = 0.25 * 1000 };
private System.Timers.Timer remote_timer = new System.Timers.Timer () { Interval = 10 * 1000 };
private DateTime last_poll = DateTime.Now;
private List<double> sizebuffer = new List<double> ();
private bool has_changed = false;
private Object change_lock = new Object ();
private Object watch_lock = new Object ();
private DateTime last_poll = DateTime.Now;
private List<double> sizebuffer = new List<double> ();
private bool has_changed = false;
private Object change_lock = new Object ();
private Object watch_lock = new Object ();
private double progress_percentage = 0.0;
private string progress_speed = "";
protected SparkleListenerBase listener;
protected SyncStatus status;
@ -64,19 +66,23 @@ namespace SparkleLib {
public abstract string CurrentRevision { get; }
public abstract bool SyncUp ();
public abstract bool SyncDown ();
public abstract double CalculateSize (DirectoryInfo parent);
public abstract bool HasUnsyncedChanges { get; set; }
public abstract List<string> ExcludePaths { get; }
public abstract double Size { get; }
public abstract double HistorySize { get; }
public abstract bool AddExclusionRule (FileSystemEventArgs args);
public abstract bool RemoveExclusionRule (FileSystemEventArgs args);
public abstract bool ExclusionRuleExists (FileSystemEventArgs args);
public delegate void SyncStatusChangedEventHandler (SyncStatus new_status);
public event SyncStatusChangedEventHandler SyncStatusChanged;
public delegate void SyncProgressChangedEventHandler (double percentage, string speed);
public event SyncProgressChangedEventHandler SyncProgressChanged;
public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set);
public event NewChangeSetEventHandler NewChangeSet;
public delegate void NewNoteEventHandler (string user_name, string user_email);
public delegate void NewNoteEventHandler (SparkleUser user);
public event NewNoteEventHandler NewNote;
public delegate void ConflictResolvedEventHandler ();
@ -108,8 +114,6 @@ namespace SparkleLib {
};
this.remote_timer.Elapsed += delegate {
string identifier = Identifier;
bool time_to_poll = (DateTime.Compare (this.last_poll,
DateTime.Now.Subtract (this.poll_interval)) < 0);
@ -122,7 +126,7 @@ namespace SparkleLib {
// In the unlikely case that we haven't synced up our
// changes or the server was down, sync up again
if (HasUnsyncedChanges)
if (HasUnsyncedChanges && !IsSyncing && this.server_online)
SyncUpBase ();
};
@ -157,6 +161,20 @@ namespace SparkleLib {
}
public double ProgressPercentage {
get {
return this.progress_percentage;
}
}
public string ProgressSpeed {
get {
return this.progress_speed;
}
}
public virtual string [] UnsyncedFilePaths {
get {
return new string [0];
@ -186,7 +204,7 @@ namespace SparkleLib {
}
public virtual bool CheckForRemoteChanges () // HasRemoteChanges { get; } ?
public virtual bool CheckForRemoteChanges () // TODO: HasRemoteChanges { get; }
{
return true;
}
@ -223,11 +241,8 @@ namespace SparkleLib {
public void Dispose ()
{
this.remote_timer.Dispose ();
this.remote_timer = null;
this.local_timer.Dispose ();
this.local_timer = null;
this.listener.Dispose ();
this.listener = null;
}
@ -301,7 +316,7 @@ namespace SparkleLib {
}
private bool IsSyncing {
public bool IsSyncing {
get {
return (Status == SyncStatus.SyncUp ||
Status == SyncStatus.SyncDown ||
@ -318,7 +333,7 @@ namespace SparkleLib {
this.sizebuffer.RemoveAt (0);
DirectoryInfo dir_info = new DirectoryInfo (LocalPath);
this.sizebuffer.Add (CalculateFolderSize (dir_info));
this.sizebuffer.Add (CalculateSize (dir_info));
if (this.sizebuffer.Count >= 4 &&
this.sizebuffer [0].Equals (this.sizebuffer [1]) &&
@ -347,42 +362,13 @@ namespace SparkleLib {
if (!this.watcher.EnableRaisingEvents)
return;
if (args.FullPath.Contains (Path.DirectorySeparatorChar + ".") &&
!args.FullPath.Contains (Path.DirectorySeparatorChar + ".notes"))
return;
string relative_path = args.FullPath.Replace (LocalPath, "");
/*
* Check whether the file which triggered the action
* is readable so that git can actually commit it
*/
try {
if(File.Exists(args.FullPath)) {
FileStream file = File.OpenRead(args.FullPath);
file.Close();
}
} catch {
if(!ExclusionRuleExists(args)) {
SparkleHelpers.DebugInfo("Warning", "File " + args.FullPath + " is not readable. Adding to ignore list.");
AddExclusionRule(args);
}
return;
}
/*
* Remove rule if file is readable but there is still
* an exclusion rule set
*/
if(ExclusionRuleExists(args)) {
SparkleHelpers.DebugInfo("Info", "Removing exclusion rule for " + args.Name);
RemoveExclusionRule(args);
// If a file was former unreadable but has now been (re)moved, skip the process.
if(!File.Exists(args.FullPath)) {
foreach (string exclude_path in ExcludePaths) {
if (relative_path.Contains (exclude_path))
return;
}
}
WatcherChangeTypes wct = args.ChangeType;
if (AnyDifferences) {
@ -402,8 +388,7 @@ namespace SparkleLib {
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + args.Name + "'");
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] Changes found, checking if settled.");
if (this.remote_timer != null)
this.remote_timer.Stop ();
this.remote_timer.Stop ();
lock (this.change_lock) {
this.has_changed = true;
@ -452,8 +437,7 @@ namespace SparkleLib {
{
try {
DisableWatching ();
if (this.remote_timer != null)
this.remote_timer.Stop ();
this.remote_timer.Stop ();
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Initiated");
@ -468,34 +452,37 @@ namespace SparkleLib {
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
if (this.listener != null)
this.listener.AnnounceBase (new SparkleAnnouncement(Identifier, CurrentRevision));
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision));
} else {
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Error");
HasUnsyncedChanges = true;
SyncDownBase ();
DisableWatching ();
if (SyncUp ()) {
if (this.server_online && SyncUp ()) {
HasUnsyncedChanges = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
if (this.listener != null)
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision));
this.listener.AnnounceBase (new SparkleAnnouncement (Identifier, CurrentRevision));
} else {
this.server_online = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
}
}
} finally {
if (this.remote_timer != null)
this.remote_timer.Start ();
this.remote_timer.Start ();
EnableWatching ();
this.progress_percentage = 0.0;
this.progress_speed = "";
}
}
@ -503,8 +490,7 @@ namespace SparkleLib {
private void SyncDownBase ()
{
SparkleHelpers.DebugInfo ("SyncDown", "[" + Name + "] Initiated");
if (this.remote_timer != null)
this.remote_timer.Stop ();
this.remote_timer.Stop ();
DisableWatching ();
if (SyncStatusChanged != null)
@ -529,7 +515,7 @@ namespace SparkleLib {
foreach (string added in change_set.Added) {
if (added.Contains (".notes")) {
if (NewNote != null)
NewNote (change_set.User.Name, change_set.User.Email);
NewNote (change_set.User);
note_added = true;
break;
@ -560,28 +546,28 @@ namespace SparkleLib {
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
if (this.remote_timer != null)
this.remote_timer.Start ();
this.remote_timer.Start ();
EnableWatching ();
this.progress_percentage = 0.0;
this.progress_speed = "";
}
public void DisableWatching ()
{
lock (watch_lock) {
lock (this.watch_lock) {
this.watcher.EnableRaisingEvents = false;
if (this.local_timer != null)
this.local_timer.Stop ();
this.local_timer.Stop ();
}
}
public void EnableWatching ()
{
lock (watch_lock) {
lock (this.watch_lock) {
this.watcher.EnableRaisingEvents = true;
if (this.local_timer != null)
this.local_timer.Start ();
this.local_timer.Start ();
}
}
@ -635,30 +621,15 @@ namespace SparkleLib {
}
// Recursively gets a folder's size in bytes
private double CalculateFolderSize (DirectoryInfo parent)
protected void OnSyncProgressChanged (double progress_percentage, string progress_speed)
{
if (!System.IO.Directory.Exists (parent.ToString ()))
return 0;
// Console.WriteLine ("OnProgressChanged: " + progress_percentage + " " + progress_speed);
double size = 0;
this.progress_percentage = progress_percentage;
this.progress_speed = progress_speed;
// 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;
foreach (FileInfo file in parent.GetFiles ()) {
if (!file.Exists)
return 0;
size += file.Length;
}
foreach (DirectoryInfo directory in parent.GetDirectories())
size += CalculateFolderSize (directory);
return size;
if (SyncProgressChanged != null)
SyncProgressChanged (progress_percentage, progress_speed);
}

View file

@ -51,7 +51,6 @@ namespace SparkleLib {
if (ChangeEvent != null)
ChangeEvent (args);
};
}
}
}

View file

@ -8,9 +8,11 @@
<string>org.sparkleshare.sparkleshare</string>
<key>CFBundleName</key>
<string>SparkleShare</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>CFBundleVersion</key>
<key>LSUIElement</key>
<string>1</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>

View file

@ -6,7 +6,6 @@ EXTRA_DIST = \
MainMenu.xib \
MainMenu.xib.designer.cs \
SparkleAbout.cs \
SparkleAlert.cs \
SparkleBadger.cs \
SparkleBubbles.cs \
SparkleController.cs \

View file

@ -57,6 +57,8 @@ namespace SparkleShare {
OrderFrontRegardless ();
MakeKeyAndOrderFront (this);
Program.UI.UpdateDockIconVisibility ();
Controller.NewVersionEvent += delegate (string new_version) {
InvokeOnMainThread (delegate {
UpdatesTextField.StringValue = "A newer version (" + new_version + ") is available!";
@ -161,6 +163,8 @@ namespace SparkleShare {
public override bool WindowShouldClose (NSObject sender)
{
(sender as SparkleAbout).OrderOut (this);
Program.UI.UpdateDockIconVisibility ();
return false;
}
}

View file

@ -1,53 +0,0 @@
// SparkleShare, a collaboration and sharing tool.
// 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
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.ObjCRuntime;
using MonoMac.WebKit;
namespace SparkleShare {
public class SparkleAlert : NSAlert {
public SparkleAlert () : base ()
{
MessageText = "SparkleShare couldn't find Git on your system. Do you want to download it?";
InformativeText = "Git is required to run SparkleShare.";
Icon = NSImage.ImageNamed ("sparkleshare.icns");
AddButton ("Download");
AddButton ("Cancel");
Buttons [0].Activated += delegate {
NSUrl url = new NSUrl ("http://code.google.com/p/git-osx-installer/downloads/list");
NSWorkspace.SharedWorkspace.OpenUrl (url);
Environment.Exit (0);
};
Buttons [1].Activated += delegate {
Environment.Exit (-1);
};
}
}
}

View file

@ -41,6 +41,21 @@ namespace SparkleShare {
public SparkleController () : base ()
{
string content_path =
Directory.GetParent (System.AppDomain.CurrentDomain.BaseDirectory)
.ToString ();
string app_path = Directory.GetParent (content_path).ToString ();
string growl_path = Path.Combine (app_path, "Frameworks", "Growl.framework", "Growl");
// Needed for Growl
Dlfcn.dlopen (growl_path, 0);
NSApplication.Init ();
// Let's use the bundled git first
SparkleBackend.DefaultBackend.Path =
Path.Combine (NSBundle.MainBundle.ResourcePath,
"git", "bin", "git");
}
@ -189,11 +204,10 @@ namespace SparkleShare {
}
new public void Quit ()
public override void OpenFile (string url)
{
this.watcher.Dispose ();
NSApplication.SharedApplication.Terminate (new NSObject ());
base.Quit ();
url = url.Replace ("%20", " ");
NSWorkspace.SharedWorkspace.OpenFile (url);
}
}
}

View file

@ -18,7 +18,6 @@
using System;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using MonoMac.Foundation;
@ -36,14 +35,17 @@ namespace SparkleShare {
PolicyDelegate = new SparkleWebPolicyDelegate ()
};
private NSBox Separator = new NSBox (new RectangleF (0, 579, 480, 1)) {
private NSBox separator = new NSBox (new RectangleF (0, 579, 480, 1)) {
BorderColor = NSColor.LightGray,
BoxType = NSBoxType.NSBoxCustom
};
private NSPopUpButton popup_button;
private NSProgressIndicator progress_indicator;
private NSTextField size_label;
private NSTextField size_label_value;
private NSTextField history_label;
private NSTextField history_label_value;
public SparkleEventLog (IntPtr handle) : base (handle) { }
@ -65,7 +67,54 @@ namespace SparkleShare {
HasShadow = true;
BackingType = NSBackingStore.Buffered;
ContentView.AddSubview (Separator);
this.size_label = new NSTextField () {
Alignment = NSTextAlignment.Right,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (0, 588, 60, 20),
StringValue = "Size:",
Font = SparkleUI.BoldFont
};
this.size_label_value = new NSTextField () {
Alignment = NSTextAlignment.Left,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (60, 588, 75, 20),
StringValue = Controller.Size,
Font = SparkleUI.Font
};
this.history_label = new NSTextField () {
Alignment = NSTextAlignment.Right,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (130, 588, 60, 20),
StringValue = "History:",
Font = SparkleUI.BoldFont
};
this.history_label_value = new NSTextField () {
Alignment = NSTextAlignment.Left,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (190, 588, 75, 20),
StringValue = Controller.HistorySize,
Font = SparkleUI.Font
};
ContentView.AddSubview (this.size_label);
ContentView.AddSubview (this.size_label_value);
ContentView.AddSubview (this.history_label);
ContentView.AddSubview (this.history_label_value);
ContentView.AddSubview (this.separator);
this.progress_indicator = new NSProgressIndicator () {
@ -81,6 +130,7 @@ namespace SparkleShare {
UpdateChooser (null);
OrderFrontRegardless ();
Program.UI.UpdateDockIconVisibility ();
// Hook up the controller events
Controller.UpdateChooserEvent += delegate (string [] folders) {
@ -103,6 +153,13 @@ namespace SparkleShare {
ContentView.AddSubview (this.progress_indicator);
});
};
Controller.UpdateSizeInfoEvent += delegate (string size, string history_size) {
InvokeOnMainThread (delegate {
this.size_label_value.StringValue = size;
this.history_label_value.StringValue = history_size;
});
};
}
@ -123,7 +180,7 @@ namespace SparkleShare {
this.popup_button.Font = NSFontManager.SharedFontManager.FontWithFamily
("Lucida Grande", NSFontTraitMask.Condensed, 0, NSFont.SmallSystemFontSize);
this.popup_button.AddItem ("All Folders");
this.popup_button.AddItem ("All Projects");
this.popup_button.Menu.AddItem (NSMenuItem.SeparatorItem);
this.popup_button.AddItems (folders);
@ -184,6 +241,8 @@ namespace SparkleShare {
public override bool WindowShouldClose (NSObject sender)
{
(sender as SparkleEventLog).OrderOut (this);
Program.UI.UpdateDockIconVisibility ();
return false;
}
}
@ -194,30 +253,7 @@ namespace SparkleShare {
public override void DecidePolicyForNavigation (WebView web_view, NSDictionary action_info,
NSUrlRequest request, WebFrame frame, NSObject decision_token)
{
string url = request.Url.ToString ();
if (url.StartsWith (Path.VolumeSeparatorChar.ToString ())) {
string file_path = request.Url.ToString ();
file_path = file_path.Replace ("%20", " ");
NSWorkspace.SharedWorkspace.OpenFile (file_path);
} else {
Regex regex = new Regex (@"(.+)~(.+)~(.+)");
Match match = regex.Match (url);
if (match.Success) {
string folder_name = match.Groups [1].Value;
string revision = match.Groups [2].Value;
string note = match.Groups [3].Value;
Thread thread = new Thread (new ThreadStart (delegate {
Program.Controller.AddNoteToFolder (folder_name, revision, note);
}));
thread.Start ();
}
}
SparkleEventLogController.LinkClicked (request.Url.ToString ());
}
}
}

View file

@ -28,8 +28,9 @@ namespace SparkleShare {
public delegate void ChangedEventHandler (string path);
public event ChangedEventHandler Changed;
private DirectoryInfo last_changed;
private FileSystemInfo last_changed;
private Thread thread;
private int poll_count = 0;
public SparkleMacWatcher (string path)
@ -50,7 +51,8 @@ namespace SparkleShare {
Changed (relative_path);
}
Thread.Sleep (2500);
Thread.Sleep (7500);
this.poll_count++;
}
}));
@ -73,7 +75,20 @@ namespace SparkleShare {
}
}
} catch (Exception) { }
if (this.poll_count >= 8) {
foreach (FileInfo info in parent.GetFiles ()) {
if (!info.FullName.Contains ("/.")) {
if (DateTime.Compare (info.LastWriteTime, this.last_changed.LastWriteTime) > 0)
this.last_changed = info;
}
}
this.poll_count = 0;
}
} catch (Exception) {
// Don't care...
}
}

View file

@ -16,11 +16,9 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Timers;
using System.Collections.Generic;
using Mono.Unix;
using MonoMac.Foundation;
@ -54,7 +52,9 @@ namespace SparkleShare {
private NSTextField PathLabel;
private NSTextField PathHelpLabel;
private NSTextField AddProjectTextField;
private Timer timer;
private NSTextField WarningTextField;
private NSImage WarningImage;
private NSImageView WarningImageView;
private NSTableView TableView;
private NSScrollView ScrollView;
private NSTableColumn IconColumn;
@ -64,7 +64,7 @@ namespace SparkleShare {
public SparkleSetup () : base ()
{
Controller.ChangePageEvent += delegate (PageType type) {
Controller.ChangePageEvent += delegate (PageType type, string [] warnings) {
InvokeOnMainThread (delegate {
Reset ();
@ -72,8 +72,8 @@ namespace SparkleShare {
case PageType.Setup: {
Header = "Welcome to SparkleShare!";
Description = "Before we can create a SparkleShare folder on this " +
"computer, we need some information from you.";
Description = "We'll need some info to mark your changes in the event log. " +
"Don't worry, this stays between you and your peers.";
FullNameLabel = new NSTextField () {
@ -87,8 +87,9 @@ namespace SparkleShare {
};
FullNameTextField = new NSTextField () {
Frame = new RectangleF (330, Frame.Height - 238, 196, 22),
StringValue = Controller.GuessedUserName
Frame = new RectangleF (330, Frame.Height - 238, 196, 22),
StringValue = Controller.GuessedUserName,
Delegate = new SparkleTextFieldDelegate ()
};
EmailLabel = new NSTextField () {
@ -102,42 +103,45 @@ namespace SparkleShare {
};
EmailTextField = new NSTextField () {
Frame = new RectangleF (330, Frame.Height - 268, 196, 22),
StringValue = Controller.GuessedUserEmail
Frame = new RectangleF (330, Frame.Height - 268, 196, 22),
StringValue = Controller.GuessedUserEmail,
Delegate = new SparkleTextFieldDelegate ()
};
// TODO: Ugly hack, do properly with events
timer = new Timer () {
Interval = 50
(FullNameTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate {
Controller.CheckSetupPage (
FullNameTextField.StringValue,
EmailTextField.StringValue
);
};
(EmailTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate {
Controller.CheckSetupPage (
FullNameTextField.StringValue,
EmailTextField.StringValue
);
};
ContinueButton = new NSButton () {
Title = "Continue",
Enabled = false
};
ContinueButton.Activated += delegate {
timer.Stop ();
timer = null;
string full_name = FullNameTextField.StringValue.Trim ();
string email = EmailTextField.StringValue.Trim ();
Controller.SetupPageCompleted (full_name, email);
};
timer.Elapsed += delegate {
Controller.UpdateSetupContinueButtonEvent += delegate (bool button_enabled) {
InvokeOnMainThread (delegate {
bool name_is_valid = !FullNameTextField.StringValue.Trim ().Equals ("");
bool email_is_valid = Program.Controller.IsValidEmail (
EmailTextField.StringValue.Trim ());
ContinueButton.Enabled = (name_is_valid && email_is_valid);
ContinueButton.Enabled = button_enabled;
});
};
timer.Start ();
ContentView.AddSubview (FullNameLabel);
ContentView.AddSubview (FullNameTextField);
@ -146,6 +150,11 @@ namespace SparkleShare {
Buttons.Add (ContinueButton);
Controller.CheckSetupPage (
FullNameTextField.StringValue,
EmailTextField.StringValue
);
break;
}
@ -168,7 +177,8 @@ namespace SparkleShare {
Frame = new RectangleF (190, Frame.Height - 336, 196, 22),
Font = SparkleUI.Font,
StringValue = Controller.PreviousAddress,
Enabled = (Controller.SelectedPlugin.Address == null)
Enabled = (Controller.SelectedPlugin.Address == null),
Delegate = new SparkleTextFieldDelegate ()
};
@ -186,12 +196,12 @@ namespace SparkleShare {
Frame = new RectangleF (190 + 196 + 16, Frame.Height - 336, 196, 22),
StringValue = Controller.PreviousPath,
Enabled = (Controller.SelectedPlugin.Path == null),
Bezeled = true
Delegate = new SparkleTextFieldDelegate ()
};
AddressTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail;
PathTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail;
AddressTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail;
PathTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail;
PathHelpLabel = new NSTextField () {
@ -210,7 +220,8 @@ namespace SparkleShare {
Frame = new RectangleF (0, 0, 0, 0),
RowHeight = 30,
IntercellSpacing = new SizeF (0, 12),
HeaderView = null
HeaderView = null,
Delegate = new SparkleTableViewDelegate ()
};
ScrollView = new NSScrollView () {
@ -270,33 +281,43 @@ namespace SparkleShare {
});
};
TableView.SelectRow (Controller.SelectedPluginIndex, false);
timer = new Timer () {
Interval = 50
(AddressTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate {
Controller.CheckAddPage (
AddressTextField.StringValue,
PathTextField.StringValue,
TableView.SelectedRow
);
};
// TODO: Use an event
timer.Elapsed += delegate {
if (TableView.SelectedRow != Controller.SelectedPluginIndex)
Controller.SelectedPluginChanged (TableView.SelectedRow);
(PathTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate {
Controller.CheckAddPage (
AddressTextField.StringValue,
PathTextField.StringValue,
TableView.SelectedRow
);
};
(TableView.Delegate as SparkleTableViewDelegate).SelectionChanged += delegate {
Controller.SelectedPluginChanged (TableView.SelectedRow);
Controller.CheckAddPage (
AddressTextField.StringValue,
PathTextField.StringValue,
TableView.SelectedRow
);
};
Controller.UpdateAddProjectButtonEvent += delegate (bool button_enabled) {
InvokeOnMainThread (delegate {
// TODO: Move checking logic to controller
if (!string.IsNullOrWhiteSpace (AddressTextField.StringValue) &&
!string.IsNullOrWhiteSpace (PathTextField.StringValue)) {
SyncButton.Enabled = true;
} else {
SyncButton.Enabled = false;
}
SyncButton.Enabled = button_enabled;
});
};
timer.Start ();
ContentView.AddSubview (ScrollView);
ContentView.AddSubview (AddressLabel);
@ -311,9 +332,6 @@ namespace SparkleShare {
};
SyncButton.Activated += delegate {
timer.Stop ();
timer = null;
Controller.AddPageCompleted (
AddressTextField.StringValue,
PathTextField.StringValue
@ -334,6 +352,13 @@ namespace SparkleShare {
Buttons.Add (CancelButton);
Controller.CheckAddPage (
AddressTextField.StringValue,
PathTextField.StringValue,
TableView.SelectedRow
);
break;
}
@ -442,6 +467,28 @@ namespace SparkleShare {
"" + Controller.SyncingFolder + " in " +
"your SparkleShare folder.";
if (warnings != null) {
WarningImage = NSImage.ImageNamed ("NSCaution");
WarningImage.Size = new SizeF (24, 24);
WarningImageView = new NSImageView () {
Image = WarningImage,
Frame = new RectangleF (190, Frame.Height - 175, 24, 24)
};
WarningTextField = new NSTextField () {
Frame = new RectangleF (230, Frame.Height - 245, 325, 100),
StringValue = warnings [0],
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Font = SparkleUI.Font
};
ContentView.AddSubview (WarningImageView);
ContentView.AddSubview (WarningTextField);
}
FinishButton = new NSButton () {
Title = "Finish"
};
@ -497,7 +544,7 @@ namespace SparkleShare {
};
string slide_image_path = Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "tutorial-slide-1.png");
"Pixmaps", "tutorial-slide-1-mac.png");
SlideImage = new NSImage (slide_image_path) {
Size = new SizeF (350, 200)
@ -529,7 +576,7 @@ namespace SparkleShare {
};
string slide_image_path = Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "tutorial-slide-2.png");
"Pixmaps", "tutorial-slide-2-mac.png");
SlideImage = new NSImage (slide_image_path) {
Size = new SizeF (350, 200)
@ -560,7 +607,7 @@ namespace SparkleShare {
};
string slide_image_path = Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "tutorial-slide-3.png");
"Pixmaps", "tutorial-slide-3-mac.png");
SlideImage = new NSImage (slide_image_path) {
Size = new SizeF (350, 200)
@ -673,4 +720,39 @@ namespace SparkleShare {
}
}
}
public class SparkleTextFieldDelegate : NSTextFieldDelegate {
public event StringValueChangedHandler StringValueChanged;
public delegate void StringValueChangedHandler ();
public override void Changed (NSNotification notification)
{
if (StringValueChanged!= null)
StringValueChanged ();
}
public override string [] GetCompletions (NSControl control, NSTextView text_view,
string [] a, MonoMac.Foundation.NSRange range, int b)
{
return new string [0];
}
}
public class SparkleTableViewDelegate : NSTableViewDelegate {
public event SelectionChangedHandler SelectionChanged;
public delegate void SelectionChangedHandler ();
public override void SelectionDidChange (NSNotification notification)
{
if (SelectionChanged != null)
SelectionChanged ();
}
}
}

View file

@ -87,6 +87,7 @@ namespace SparkleShare {
MakeKeyAndOrderFront (this);
OrderFrontRegardless ();
Program.UI.UpdateDockIconVisibility ();
}
@ -143,8 +144,10 @@ namespace SparkleShare {
public override void PerformClose (NSObject sender)
{
OrderOut (this);
base.OrderOut (this);
NSApplication.SharedApplication.RemoveWindowsItem (this);
Program.UI.UpdateDockIconVisibility ();
return;
}

View file

@ -11,7 +11,7 @@
<RootNamespace>SparkleShare</RootNamespace>
<AssemblyName>SparkleShare</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ReleaseVersion>0.2.5</ReleaseVersion>
<ReleaseVersion>0.6.0</ReleaseVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -24,7 +24,7 @@
<ConsolePause>false</ConsolePause>
<CustomCommands>
<CustomCommands>
<Command type="AfterBuild" command="mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Frameworks; cp -r Growl.framework ${TargetDir}/${SolutionName}.app/Contents/Frameworks" externalConsole="true" />
<Command type="AfterBuild" command="mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Frameworks; cp -r Growl.framework ${TargetDir}/${SolutionName}.app/Contents/Frameworks;mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Resources; cp -r git ${TargetDir}/${SolutionName}.app/Contents/Resources" externalConsole="true" />
</CustomCommands>
</CustomCommands>
</PropertyGroup>
@ -58,15 +58,11 @@
<Reference Include="Mono.Posix">
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Meebey.SmartIrc4net, Version=0.4.5.0, Culture=neutral, PublicKeyToken=7868485fbf407e0f">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\bin\Meebey.SmartIrc4net.dll</HintPath>
</Reference>
<Reference Include="SparkleLib, Version=0.2.5.0, Culture=neutral, PublicKeyToken=null">
<Reference Include="System.Net" />
<Reference Include="SparkleLib, Version=0.6.0.0, Culture=neutral, PublicKeyToken=null">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\bin\SparkleLib.dll</HintPath>
</Reference>
<Reference Include="System.Net" />
</ItemGroup>
<ItemGroup>
<Compile Include="AppDelegate.cs">
@ -83,8 +79,6 @@
<Compile Include="..\Program.cs">
<Link>Program.cs</Link>
</Compile>
<Compile Include="SparkleAbout.cs" />
<Compile Include="SparkleAlert.cs" />
<Compile Include="SparkleMacWatcher.cs" />
<Compile Include="SparkleEventLog.cs" />
<Compile Include="SparkleBadger.cs" />
@ -113,6 +107,13 @@
<Compile Include="..\SparklePlugin.cs">
<Link>SparklePlugin.cs</Link>
</Compile>
<Compile Include="..\SparkleOptions.cs">
<Link>SparkleOptions.cs</Link>
</Compile>
<Compile Include="SparkleAbout.cs" />
<Compile Include="..\SparkleInvite.cs">
<Link>SparkleInvite.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Include="MainMenu.xib" />
@ -277,6 +278,15 @@
<Content Include="..\..\data\tutorial-slide-1.png">
<Link>Pixmaps\tutorial-slide-1.png</Link>
</Content>
<Content Include="..\..\data\tutorial-slide-1-mac.png">
<Link>Pixmaps\tutorial-slide-1-mac.png</Link>
</Content>
<Content Include="..\..\data\tutorial-slide-2-mac.png">
<Link>Pixmaps\tutorial-slide-2-mac.png</Link>
</Content>
<Content Include="..\..\data\tutorial-slide-3-mac.png">
<Link>Pixmaps\tutorial-slide-3-mac.png</Link>
</Content>
<Content Include="..\..\data\tutorial-slide-2.png">
<Link>Pixmaps\tutorial-slide-2.png</Link>
</Content>

2
SparkleShare/Mac/SparkleShare.sln Executable file → Normal file
View file

@ -16,6 +16,6 @@ Global
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = SparkleShare.csproj
version = 0.2.5
version = 0.6.0
EndGlobalSection
EndGlobal

View file

@ -46,6 +46,7 @@ namespace SparkleShare {
private NSMenuItem AboutMenuItem;
private NSMenuItem NotificationsMenuItem;
private NSMenuItem RecentEventsMenuItem;
private NSMenuItem QuitMenuItem;
private NSImage [] AnimationFrames;
private NSImage [] AnimationFramesActive;
private NSImage ErrorImage;
@ -110,7 +111,10 @@ namespace SparkleShare {
case IconState.Syncing:
StateText = _("Syncing…");
StateText = _("Syncing… " +
Controller.ProgressPercentage + "% " +
Controller.ProgressSpeed);
StateMenuItem.Title = StateText;
if (!Animation.Enabled)
@ -119,6 +123,8 @@ namespace SparkleShare {
break;
case IconState.Error:
Animation.Stop ();
StateText = _("Not everything is synced");
StateMenuItem.Title = StateText;
@ -240,7 +246,7 @@ namespace SparkleShare {
RecentEventsMenuItem = new NSMenuItem () {
Title = "Open Recent Events",
Enabled = true
Enabled = (Controller.Folders.Length > 0)
};
if (Controller.Folders.Length > 0) {
@ -298,10 +304,22 @@ namespace SparkleShare {
});
};
Menu.AddItem (AboutMenuItem);
Menu.AddItem (NSMenuItem.SeparatorItem);
QuitMenuItem = new NSMenuItem () {
Title = "Quit",
Enabled = true
};
Menu.AddItem (AboutMenuItem);
QuitMenuItem.Activated += delegate {
Program.Controller.Quit ();
};
Menu.AddItem (QuitMenuItem);
StatusItem.Menu = Menu;
StatusItem.Menu.Update ();
}

View file

@ -37,22 +37,11 @@ namespace SparkleShare {
public static SparkleBubbles Bubbles;
public static SparkleAbout About;
public static NSFont Font;
private NSAlert alert;
public static NSFont BoldFont;
public SparkleUI ()
{
string content_path = Directory.GetParent (
System.AppDomain.CurrentDomain.BaseDirectory).ToString ();
string app_path = Directory.GetParent (content_path).ToString ();
string growl_path = Path.Combine (app_path, "Frameworks", "Growl.framework", "Growl");
// Needed for Growl
Dlfcn.dlopen (growl_path, 0);
NSApplication.Init ();
// Use translations
Catalog.Init ("sparkleshare",
Path.Combine (NSBundle.MainBundle.ResourcePath, "Translations"));
@ -66,17 +55,14 @@ namespace SparkleShare {
NSApplication.SharedApplication.ApplicationIconImage
= NSImage.ImageNamed ("sparkleshare.icns");
if (!Program.Controller.BackendIsPresent) {
this.alert = new SparkleAlert ();
this.alert.RunModal ();
return;
}
SetFolderIcon ();
Font = NSFontManager.SharedFontManager.FontWithFamily
("Lucida Grande", NSFontTraitMask.Condensed, 0, 13);
BoldFont = NSFontManager.SharedFontManager.FontWithFamily
("Lucida Grande", NSFontTraitMask.Bold, 0, 13);
StatusIcon = new SparkleStatusIcon ();
Bubbles = new SparkleBubbles ();
@ -106,6 +92,29 @@ namespace SparkleShare {
}
public void UpdateDockIconVisibility ()
{
if (true) { // TODO: check for open windows
ShowDockIcon ();
} else {
HideDockIcon ();
}
}
private void HideDockIcon () {
// Currently not supported, here for completeness sake (see Apple's docs)
// NSApplication.SharedApplication.ActivationPolicy = NSApplicationActivationPolicy.None;
}
private void ShowDockIcon () {
NSApplication.SharedApplication.ActivationPolicy = NSApplicationActivationPolicy.Regular;
}
[Export("registrationDictionaryForGrowl")]
NSDictionary RegistrationDictionaryForGrowl ()
{
@ -122,6 +131,7 @@ namespace SparkleShare {
NSApplication.SharedApplication.DockTile.BadgeLabel = null;
}
public override void WillTerminate (NSNotification notification)
{
Program.Controller.Quit ();

View file

@ -0,0 +1,370 @@
From https://github.com/gitster/git:
////////////////////////////////////////////////////////////////
GIT - the stupid content tracker
////////////////////////////////////////////////////////////////
"git" can mean anything, depending on your mood.
- random three-letter combination that is pronounceable, and not
actually used by any common UNIX command. The fact that it is a
mispronunciation of "get" may or may not be relevant.
- stupid. contemptible and despicable. simple. Take your pick from the
dictionary of slang.
- "global information tracker": you're in a good mood, and it actually
works for you. Angels sing, and a light suddenly fills the room.
- "goddamn idiotic truckload of sh*t": when it breaks
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
Git is an Open Source project covered by the GNU General Public License.
It was originally written by Linus Torvalds with help of a group of
hackers around the net. It is currently maintained by Junio C Hamano.
////////////////////////////////////////////////////////////////
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 2 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 along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

BIN
SparkleShare/Mac/git/bin/git Executable file

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
git

Binary file not shown.

View file

@ -0,0 +1 @@
git

Binary file not shown.

11702
SparkleShare/Mac/git/bin/gitk Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,841 @@
#!/bin/sh
#
# Copyright (c) 2005, 2006 Junio C Hamano
SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
git am [options] [(<mbox>|<Maildir>)...]
git am [options] (--resolved | --skip | --abort)
--
i,interactive run interactively
b,binary* (historical option -- no-op)
3,3way allow fall back on 3way merging if needed
q,quiet be quiet
s,signoff add a Signed-off-by line to the commit message
u,utf8 recode into utf8 (default)
k,keep pass -k flag to git-mailinfo
keep-cr pass --keep-cr flag to git-mailsplit for mbox format
no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
c,scissors strip everything before a scissors line
whitespace= pass it through git-apply
ignore-space-change pass it through git-apply
ignore-whitespace pass it through git-apply
directory= pass it through git-apply
C= pass it through git-apply
p= pass it through git-apply
patch-format= format the patch(es) are in
reject pass it through git-apply
resolvemsg= override error message when patch failure occurs
continue continue applying patches after resolving a conflict
r,resolved synonyms for --continue
skip skip the current patch
abort restore the original branch and abort the patching operation.
committer-date-is-author-date lie about committer date
ignore-date use current timestamp for author date
rerere-autoupdate update the index with reused conflict resolution if possible
rebasing* (internal use for git-rebase)"
. git-sh-setup
prefix=$(git rev-parse --show-prefix)
set_reflog_action am
require_work_tree
cd_to_toplevel
git var GIT_COMMITTER_IDENT >/dev/null ||
die "You need to set your committer info first"
if git rev-parse --verify -q HEAD >/dev/null
then
HAS_HEAD=yes
else
HAS_HEAD=
fi
cmdline="git am"
if test '' != "$interactive"
then
cmdline="$cmdline -i"
fi
if test '' != "$threeway"
then
cmdline="$cmdline -3"
fi
sq () {
git rev-parse --sq-quote "$@"
}
stop_here () {
echo "$1" >"$dotest/next"
git rev-parse --verify -q HEAD >"$dotest/abort-safety"
exit 1
}
safe_to_abort () {
if test -f "$dotest/dirtyindex"
then
return 1
fi
if ! test -s "$dotest/abort-safety"
then
return 0
fi
abort_safety=$(cat "$dotest/abort-safety")
if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety"
then
return 0
fi
echo >&2 "You seem to have moved HEAD since the last 'am' failure."
echo >&2 "Not rewinding to ORIG_HEAD"
return 1
}
stop_here_user_resolve () {
if [ -n "$resolvemsg" ]; then
printf '%s\n' "$resolvemsg"
stop_here $1
fi
echo "When you have resolved this problem run \"$cmdline --resolved\"."
echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
stop_here $1
}
go_next () {
rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
"$dotest/patch" "$dotest/info"
echo "$next" >"$dotest/next"
this=$next
}
cannot_fallback () {
echo "$1"
echo "Cannot fall back to three-way merge."
exit 1
}
fall_back_3way () {
O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
rm -fr "$dotest"/patch-merge-*
mkdir "$dotest/patch-merge-tmp-dir"
# First see if the patch records the index info that we can use.
git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
"$dotest/patch" &&
GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
git write-tree >"$dotest/patch-merge-base+" ||
cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
say Using index info to reconstruct a base tree...
if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
git apply --cached <"$dotest/patch"
then
mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
else
cannot_fallback "Did you hand edit your patch?
It does not apply to blobs recorded in its index."
fi
test -f "$dotest/patch-merge-index" &&
his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
orig_tree=$(cat "$dotest/patch-merge-base") &&
rm -fr "$dotest"/patch-merge-* || exit 1
say Falling back to patching base and 3-way merge...
# This is not so wrong. Depending on which base we picked,
# orig_tree may be wildly different from ours, but his_tree
# has the same set of wildly different changes in parts the
# patch did not touch, so recursive ends up canceling them,
# saying that we reverted all those changes.
eval GITHEAD_$his_tree='"$FIRSTLINE"'
export GITHEAD_$his_tree
if test -n "$GIT_QUIET"
then
GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
fi
git-merge-recursive $orig_tree -- HEAD $his_tree || {
git rerere $allow_rerere_autoupdate
echo Failed to merge in the changes.
exit 1
}
unset GITHEAD_$his_tree
}
clean_abort () {
test $# = 0 || echo >&2 "$@"
rm -fr "$dotest"
exit 1
}
patch_format=
check_patch_format () {
# early return if patch_format was set from the command line
if test -n "$patch_format"
then
return 0
fi
# we default to mbox format if input is from stdin and for
# directories
if test $# = 0 || test "x$1" = "x-" || test -d "$1"
then
patch_format=mbox
return 0
fi
# otherwise, check the first few lines of the first patch to try
# to detect its format
{
read l1
read l2
read l3
case "$l1" in
"From "* | "From: "*)
patch_format=mbox
;;
'# This series applies on GIT commit'*)
patch_format=stgit-series
;;
"# HG changeset patch")
patch_format=hg
;;
*)
# if the second line is empty and the third is
# a From, Author or Date entry, this is very
# likely an StGIT patch
case "$l2,$l3" in
,"From: "* | ,"Author: "* | ,"Date: "*)
patch_format=stgit
;;
*)
;;
esac
;;
esac
if test -z "$patch_format" &&
test -n "$l1" &&
test -n "$l2" &&
test -n "$l3"
then
# This begins with three non-empty lines. Is this a
# piece of e-mail a-la RFC2822? Grab all the headers,
# discarding the indented remainder of folded lines,
# and see if it looks like that they all begin with the
# header field names...
tr -d '\015' <"$1" |
sed -n -e '/^$/q' -e '/^[ ]/d' -e p |
sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
patch_format=mbox
fi
} < "$1" || clean_abort
}
split_patches () {
case "$patch_format" in
mbox)
if test -n "$rebasing" || test t = "$keepcr"
then
keep_cr=--keep-cr
else
keep_cr=
fi
git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" ||
clean_abort
;;
stgit-series)
if test $# -ne 1
then
clean_abort "Only one StGIT patch series can be applied at once"
fi
series_dir=`dirname "$1"`
series_file="$1"
shift
{
set x
while read filename
do
set "$@" "$series_dir/$filename"
done
# remove the safety x
shift
# remove the arg coming from the first-line comment
shift
} < "$series_file" || clean_abort
# set the patch format appropriately
patch_format=stgit
# now handle the actual StGIT patches
split_patches "$@"
;;
stgit)
this=0
for stgit in "$@"
do
this=`expr "$this" + 1`
msgnum=`printf "%0${prec}d" $this`
# Perl version of StGIT parse_patch. The first nonemptyline
# not starting with Author, From or Date is the
# subject, and the body starts with the next nonempty
# line not starting with Author, From or Date
perl -ne 'BEGIN { $subject = 0 }
if ($subject > 1) { print ; }
elsif (/^\s+$/) { next ; }
elsif (/^Author:/) { print s/Author/From/ ; }
elsif (/^(From|Date)/) { print ; }
elsif ($subject) {
$subject = 2 ;
print "\n" ;
print ;
} else {
print "Subject: ", $_ ;
$subject = 1;
}
' < "$stgit" > "$dotest/$msgnum" || clean_abort
done
echo "$this" > "$dotest/last"
this=
msgnum=
;;
*)
if test -n "$parse_patch" ; then
clean_abort "Patch format $patch_format is not supported."
else
clean_abort "Patch format detection failed."
fi
;;
esac
}
prec=4
dotest="$GIT_DIR/rebase-apply"
sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort=
resolvemsg= resume= scissors= no_inbody_headers=
git_apply_opt=
committer_date_is_author_date=
ignore_date=
allow_rerere_autoupdate=
if test "$(git config --bool --get am.keepcr)" = true
then
keepcr=t
fi
while test $# != 0
do
case "$1" in
-i|--interactive)
interactive=t ;;
-b|--binary)
: ;;
-3|--3way)
threeway=t ;;
-s|--signoff)
sign=t ;;
-u|--utf8)
utf8=t ;; # this is now default
--no-utf8)
utf8= ;;
-k|--keep)
keep=t ;;
-c|--scissors)
scissors=t ;;
--no-scissors)
scissors=f ;;
-r|--resolved|--continue)
resolved=t ;;
--skip)
skip=t ;;
--abort)
abort=t ;;
--rebasing)
rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
-d|--dotest)
die "-d option is no longer supported. Do not use."
;;
--resolvemsg)
shift; resolvemsg=$1 ;;
--whitespace|--directory)
git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
-C|-p)
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
--patch-format)
shift ; patch_format="$1" ;;
--reject|--ignore-whitespace|--ignore-space-change)
git_apply_opt="$git_apply_opt $1" ;;
--committer-date-is-author-date)
committer_date_is_author_date=t ;;
--ignore-date)
ignore_date=t ;;
--rerere-autoupdate|--no-rerere-autoupdate)
allow_rerere_autoupdate="$1" ;;
-q|--quiet)
GIT_QUIET=t ;;
--keep-cr)
keepcr=t ;;
--no-keep-cr)
keepcr=f ;;
--)
shift; break ;;
*)
usage ;;
esac
shift
done
# If the dotest directory exists, but we have finished applying all the
# patches in them, clear it out.
if test -d "$dotest" &&
last=$(cat "$dotest/last") &&
next=$(cat "$dotest/next") &&
test $# != 0 &&
test "$next" -gt "$last"
then
rm -fr "$dotest"
fi
if test -d "$dotest"
then
case "$#,$skip$resolved$abort" in
0,*t*)
# Explicit resume command and we do not have file, so
# we are happy.
: ;;
0,)
# No file input but without resume parameters; catch
# user error to feed us a patch from standard input
# when there is already $dotest. This is somewhat
# unreliable -- stdin could be /dev/null for example
# and the caller did not intend to feed us a patch but
# wanted to continue unattended.
test -t 0
;;
*)
false
;;
esac ||
die "previous rebase directory $dotest still exists but mbox given."
resume=yes
case "$skip,$abort" in
t,t)
die "Please make up your mind. --skip or --abort?"
;;
t,)
git rerere clear
git read-tree --reset -u HEAD HEAD
orig_head=$(cat "$GIT_DIR/ORIG_HEAD")
git reset HEAD
git update-ref ORIG_HEAD $orig_head
;;
,t)
if test -f "$dotest/rebasing"
then
exec git rebase --abort
fi
git rerere clear
if safe_to_abort
then
git read-tree --reset -u HEAD ORIG_HEAD
git reset ORIG_HEAD
fi
rm -fr "$dotest"
exit ;;
esac
rm -f "$dotest/dirtyindex"
else
# Make sure we are not given --skip, --resolved, nor --abort
test "$skip$resolved$abort" = "" ||
die "Resolve operation not in progress, we are not resuming."
# Start afresh.
mkdir -p "$dotest" || exit
if test -n "$prefix" && test $# != 0
then
first=t
for arg
do
test -n "$first" && {
set x
first=
}
if is_absolute_path "$arg"
then
set "$@" "$arg"
else
set "$@" "$prefix$arg"
fi
done
shift
fi
check_patch_format "$@"
split_patches "$@"
# -i can and must be given when resuming; everything
# else is kept
echo " $git_apply_opt" >"$dotest/apply-opt"
echo "$threeway" >"$dotest/threeway"
echo "$sign" >"$dotest/sign"
echo "$utf8" >"$dotest/utf8"
echo "$keep" >"$dotest/keep"
echo "$keepcr" >"$dotest/keepcr"
echo "$scissors" >"$dotest/scissors"
echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
echo "$GIT_QUIET" >"$dotest/quiet"
echo 1 >"$dotest/next"
if test -n "$rebasing"
then
: >"$dotest/rebasing"
else
: >"$dotest/applying"
if test -n "$HAS_HEAD"
then
git update-ref ORIG_HEAD HEAD
else
git update-ref -d ORIG_HEAD >/dev/null 2>&1
fi
fi
fi
case "$resolved" in
'')
case "$HAS_HEAD" in
'')
files=$(git ls-files) ;;
?*)
files=$(git diff-index --cached --name-only HEAD --) ;;
esac || exit
if test "$files"
then
test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
die "Dirty index: cannot apply patches (dirty: $files)"
fi
esac
if test "$(cat "$dotest/utf8")" = t
then
utf8=-u
else
utf8=-n
fi
if test "$(cat "$dotest/keep")" = t
then
keep=-k
fi
case "$(cat "$dotest/keepcr")" in
t)
keepcr=--keep-cr ;;
f)
keepcr=--no-keep-cr ;;
esac
case "$(cat "$dotest/scissors")" in
t)
scissors=--scissors ;;
f)
scissors=--no-scissors ;;
esac
if test "$(cat "$dotest/no_inbody_headers")" = t
then
no_inbody_headers=--no-inbody-headers
else
no_inbody_headers=
fi
if test "$(cat "$dotest/quiet")" = t
then
GIT_QUIET=t
fi
if test "$(cat "$dotest/threeway")" = t
then
threeway=t
fi
git_apply_opt=$(cat "$dotest/apply-opt")
if test "$(cat "$dotest/sign")" = t
then
SIGNOFF=`git var GIT_COMMITTER_IDENT | sed -e '
s/>.*/>/
s/^/Signed-off-by: /'
`
else
SIGNOFF=
fi
last=`cat "$dotest/last"`
this=`cat "$dotest/next"`
if test "$skip" = t
then
this=`expr "$this" + 1`
resume=
fi
while test "$this" -le "$last"
do
msgnum=`printf "%0${prec}d" $this`
next=`expr "$this" + 1`
test -f "$dotest/$msgnum" || {
resume=
go_next
continue
}
# If we are not resuming, parse and extract the patch information
# into separate files:
# - info records the authorship and title
# - msg is the rest of commit log message
# - patch is the patch body.
#
# When we are resuming, these files are either already prepared
# by the user, or the user can tell us to do so by --resolved flag.
case "$resume" in
'')
git mailinfo $keep $no_inbody_headers $scissors $utf8 "$dotest/msg" "$dotest/patch" \
<"$dotest/$msgnum" >"$dotest/info" ||
stop_here $this
# skip pine's internal folder data
sane_grep '^Author: Mail System Internal Data$' \
<"$dotest"/info >/dev/null &&
go_next && continue
test -s "$dotest/patch" || {
echo "Patch is empty. Was it split wrong?"
echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
stop_here $this
}
rm -f "$dotest/original-commit" "$dotest/author-script"
if test -f "$dotest/rebasing" &&
commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
-e q "$dotest/$msgnum") &&
test "$(git cat-file -t "$commit")" = commit
then
git cat-file commit "$commit" |
sed -e '1,/^$/d' >"$dotest/msg-clean"
echo "$commit" > "$dotest/original-commit"
get_author_ident_from_commit "$commit" > "$dotest/author-script"
else
{
sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
echo
cat "$dotest/msg"
} |
git stripspace > "$dotest/msg-clean"
fi
;;
esac
if test -f "$dotest/author-script"
then
eval $(cat "$dotest/author-script")
else
GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
fi
if test -z "$GIT_AUTHOR_EMAIL"
then
echo "Patch does not have a valid e-mail address."
stop_here $this
fi
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
case "$resume" in
'')
if test '' != "$SIGNOFF"
then
LAST_SIGNED_OFF_BY=`
sed -ne '/^Signed-off-by: /p' \
"$dotest/msg-clean" |
sed -ne '$p'
`
ADD_SIGNOFF=`
test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
test '' = "$LAST_SIGNED_OFF_BY" && echo
echo "$SIGNOFF"
}`
else
ADD_SIGNOFF=
fi
{
if test -s "$dotest/msg-clean"
then
cat "$dotest/msg-clean"
fi
if test '' != "$ADD_SIGNOFF"
then
echo "$ADD_SIGNOFF"
fi
} >"$dotest/final-commit"
;;
*)
case "$resolved$interactive" in
tt)
# This is used only for interactive view option.
git diff-index -p --cached HEAD -- >"$dotest/patch"
;;
esac
esac
resume=
if test "$interactive" = t
then
test -t 0 ||
die "cannot be interactive without stdin connected to a terminal."
action=again
while test "$action" = again
do
echo "Commit Body is:"
echo "--------------------------"
cat "$dotest/final-commit"
echo "--------------------------"
printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
read reply
case "$reply" in
[yY]*) action=yes ;;
[aA]*) action=yes interactive= ;;
[nN]*) action=skip ;;
[eE]*) git_editor "$dotest/final-commit"
action=again ;;
[vV]*) action=again
git_pager "$dotest/patch" ;;
*) action=again ;;
esac
done
else
action=yes
fi
if test -f "$dotest/final-commit"
then
FIRSTLINE=$(sed 1q "$dotest/final-commit")
else
FIRSTLINE=""
fi
if test $action = skip
then
go_next
continue
fi
if test -x "$GIT_DIR"/hooks/applypatch-msg
then
"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
stop_here $this
fi
say "Applying: $FIRSTLINE"
case "$resolved" in
'')
# When we are allowed to fall back to 3-way later, don't give
# false errors during the initial attempt.
squelch=
if test "$threeway" = t
then
squelch='>/dev/null 2>&1 '
fi
eval "git apply $squelch$git_apply_opt"' --index "$dotest/patch"'
apply_status=$?
;;
t)
# Resolved means the user did all the hard work, and
# we do not have to do any patch application. Just
# trust what the user has in the index file and the
# working tree.
resolved=
git diff-index --quiet --cached HEAD -- && {
echo "No changes - did you forget to use 'git add'?"
echo "If there is nothing left to stage, chances are that something else"
echo "already introduced the same changes; you might want to skip this patch."
stop_here_user_resolve $this
}
unmerged=$(git ls-files -u)
if test -n "$unmerged"
then
echo "You still have unmerged paths in your index"
echo "did you forget to use 'git add'?"
stop_here_user_resolve $this
fi
apply_status=0
git rerere
;;
esac
if test $apply_status != 0 && test "$threeway" = t
then
if (fall_back_3way)
then
# Applying the patch to an earlier tree and merging the
# result may have produced the same tree as ours.
git diff-index --quiet --cached HEAD -- && {
say No changes -- Patch already applied.
go_next
continue
}
# clear apply_status -- we have successfully merged.
apply_status=0
fi
fi
if test $apply_status != 0
then
printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
stop_here_user_resolve $this
fi
if test -x "$GIT_DIR"/hooks/pre-applypatch
then
"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
fi
tree=$(git write-tree) &&
commit=$(
if test -n "$ignore_date"
then
GIT_AUTHOR_DATE=
fi
parent=$(git rev-parse --verify -q HEAD) ||
say >&2 "applying to an empty history"
if test -n "$committer_date_is_author_date"
then
GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
export GIT_COMMITTER_DATE
fi &&
git commit-tree $tree ${parent:+-p} $parent <"$dotest/final-commit"
) &&
git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
stop_here $this
if test -f "$dotest/original-commit"; then
echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
fi
if test -x "$GIT_DIR"/hooks/post-applypatch
then
"$GIT_DIR"/hooks/post-applypatch
fi
go_next
done
if test -s "$dotest"/rewritten; then
git notes copy --for-rewrite=rebase < "$dotest"/rewritten
if test -x "$GIT_DIR"/hooks/post-rewrite; then
"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
fi
fi
rm -fr "$dotest"
git gc --auto

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1,457 @@
#!/bin/sh
USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
LONG_USAGE='git bisect help
print this long help message.
git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
reset bisect state and start bisection.
git bisect bad [<rev>]
mark <rev> a known-bad revision.
git bisect good [<rev>...]
mark <rev>... known-good revisions.
git bisect skip [(<rev>|<range>)...]
mark <rev>... untestable revisions.
git bisect next
find next bisection to test and check it out.
git bisect reset [<commit>]
finish bisection search and go back to commit.
git bisect visualize
show bisect status in gitk.
git bisect replay <logfile>
replay bisection log.
git bisect log
show bisect log.
git bisect run <cmd>...
use <cmd>... to automatically bisect.
Please use "git help bisect" to get the full man page.'
OPTIONS_SPEC=
. git-sh-setup
require_work_tree
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
bisect_autostart() {
test -s "$GIT_DIR/BISECT_START" || {
echo >&2 'You need to start by "git bisect start"'
if test -t 0
then
echo >&2 -n 'Do you want me to do it for you [Y/n]? '
read yesno
case "$yesno" in
[Nn]*)
exit ;;
esac
bisect_start
else
exit 1
fi
}
}
bisect_start() {
#
# Verify HEAD.
#
head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
die "Bad HEAD - I need a HEAD"
#
# Check if we are bisecting.
#
start_head=''
if test -s "$GIT_DIR/BISECT_START"
then
# Reset to the rev from where we started.
start_head=$(cat "$GIT_DIR/BISECT_START")
git checkout "$start_head" -- || exit
else
# Get rev from where we start.
case "$head" in
refs/heads/*|$_x40)
# This error message should only be triggered by
# cogito usage, and cogito users should understand
# it relates to cg-seek.
[ -s "$GIT_DIR/head-name" ] &&
die "won't bisect on seeked tree"
start_head="${head#refs/heads/}"
;;
*)
die "Bad HEAD - strange symbolic ref"
;;
esac
fi
#
# Get rid of any old bisect state.
#
bisect_clean_state || exit
#
# Check for one bad and then some good revisions.
#
has_double_dash=0
for arg; do
case "$arg" in --) has_double_dash=1; break ;; esac
done
orig_args=$(git rev-parse --sq-quote "$@")
bad_seen=0
eval=''
while [ $# -gt 0 ]; do
arg="$1"
case "$arg" in
--)
shift
break
;;
*)
rev=$(git rev-parse -q --verify "$arg^{commit}") || {
test $has_double_dash -eq 1 &&
die "'$arg' does not appear to be a valid revision"
break
}
case $bad_seen in
0) state='bad' ; bad_seen=1 ;;
*) state='good' ;;
esac
eval="$eval bisect_write '$state' '$rev' 'nolog'; "
shift
;;
esac
done
#
# Change state.
# In case of mistaken revs or checkout error, or signals received,
# "bisect_auto_next" below may exit or misbehave.
# We have to trap this to be able to clean up using
# "bisect_clean_state".
#
trap 'bisect_clean_state' 0
trap 'exit 255' 1 2 3 15
#
# Write new start state.
#
echo "$start_head" >"$GIT_DIR/BISECT_START" &&
git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
eval "$eval" &&
echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
#
# Check if we can proceed to the next bisect state.
#
bisect_auto_next
trap '-' 0
}
bisect_write() {
state="$1"
rev="$2"
nolog="$3"
case "$state" in
bad) tag="$state" ;;
good|skip) tag="$state"-"$rev" ;;
*) die "Bad bisect_write argument: $state" ;;
esac
git update-ref "refs/bisect/$tag" "$rev" || exit
echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
}
is_expected_rev() {
test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
}
check_expected_revs() {
for _rev in "$@"; do
if ! is_expected_rev "$_rev"; then
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
return
fi
done
}
bisect_skip() {
all=''
for arg in "$@"
do
case "$arg" in
*..*)
revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
*)
revs=$(git rev-parse --sq-quote "$arg") ;;
esac
all="$all $revs"
done
eval bisect_state 'skip' $all
}
bisect_state() {
bisect_autostart
state=$1
case "$#,$state" in
0,*)
die "Please call 'bisect_state' with at least one argument." ;;
1,bad|1,good|1,skip)
rev=$(git rev-parse --verify HEAD) ||
die "Bad rev input: HEAD"
bisect_write "$state" "$rev"
check_expected_revs "$rev" ;;
2,bad|*,good|*,skip)
shift
eval=''
for rev in "$@"
do
sha=$(git rev-parse --verify "$rev^{commit}") ||
die "Bad rev input: $rev"
eval="$eval bisect_write '$state' '$sha'; "
done
eval "$eval"
check_expected_revs "$@" ;;
*,bad)
die "'git bisect bad' can take only one argument." ;;
*)
usage ;;
esac
bisect_auto_next
}
bisect_next_check() {
missing_good= missing_bad=
git show-ref -q --verify refs/bisect/bad || missing_bad=t
test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
case "$missing_good,$missing_bad,$1" in
,,*)
: have both good and bad - ok
;;
*,)
# do not have both but not asked to fail - just report.
false
;;
t,,good)
# have bad but not good. we could bisect although
# this is less optimum.
echo >&2 'Warning: bisecting only with a bad commit.'
if test -t 0
then
printf >&2 'Are you sure [Y/n]? '
read yesno
case "$yesno" in [Nn]*) exit 1 ;; esac
fi
: bisect without good...
;;
*)
THEN=''
test -s "$GIT_DIR/BISECT_START" || {
echo >&2 'You need to start by "git bisect start".'
THEN='then '
}
echo >&2 'You '$THEN'need to give me at least one good' \
'and one bad revisions.'
echo >&2 '(You can use "git bisect bad" and' \
'"git bisect good" for that.)'
exit 1 ;;
esac
}
bisect_auto_next() {
bisect_next_check && bisect_next || :
}
bisect_next() {
case "$#" in 0) ;; *) usage ;; esac
bisect_autostart
bisect_next_check good
# Perform all bisection computation, display and checkout
git bisect--helper --next-all
res=$?
# Check if we should exit because bisection is finished
test $res -eq 10 && exit 0
# Check for an error in the bisection process
test $res -ne 0 && exit $res
return 0
}
bisect_visualize() {
bisect_next_check fail
if test $# = 0
then
case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
'') set git log ;;
set*) set gitk ;;
esac
else
case "$1" in
git*|tig) ;;
-*) set git log "$@" ;;
*) set git "$@" ;;
esac
fi
eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
}
bisect_reset() {
test -s "$GIT_DIR/BISECT_START" || {
echo "We are not bisecting."
return
}
case "$#" in
0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
die "'$1' is not a valid commit"
branch="$1" ;;
*)
usage ;;
esac
if git checkout "$branch" -- ; then
bisect_clean_state
else
die "Could not check out original HEAD '$branch'." \
"Try 'git bisect reset <commit>'."
fi
}
bisect_clean_state() {
# There may be some refs packed during bisection.
git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
while read ref hash
do
git update-ref -d $ref $hash || exit
done
rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
rm -f "$GIT_DIR/BISECT_LOG" &&
rm -f "$GIT_DIR/BISECT_NAMES" &&
rm -f "$GIT_DIR/BISECT_RUN" &&
# Cleanup head-name if it got left by an old version of git-bisect
rm -f "$GIT_DIR/head-name" &&
rm -f "$GIT_DIR/BISECT_START"
}
bisect_replay () {
test "$#" -eq 1 || die "No logfile given"
test -r "$1" || die "cannot read $1 for replaying"
bisect_reset
while read git bisect command rev
do
test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
if test "$git" = "git-bisect"; then
rev="$command"
command="$bisect"
fi
case "$command" in
start)
cmd="bisect_start $rev"
eval "$cmd" ;;
good|bad|skip)
bisect_write "$command" "$rev" ;;
*)
die "?? what are you talking about?" ;;
esac
done <"$1"
bisect_auto_next
}
bisect_run () {
bisect_next_check fail
while true
do
echo "running $@"
"$@"
res=$?
# Check for really bad run error.
if [ $res -lt 0 -o $res -ge 128 ]; then
echo >&2 "bisect run failed:"
echo >&2 "exit code $res from '$@' is < 0 or >= 128"
exit $res
fi
# Find current state depending on run success or failure.
# A special exit code of 125 means cannot test.
if [ $res -eq 125 ]; then
state='skip'
elif [ $res -gt 0 ]; then
state='bad'
else
state='good'
fi
# We have to use a subshell because "bisect_state" can exit.
( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
res=$?
cat "$GIT_DIR/BISECT_RUN"
if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
> /dev/null; then
echo >&2 "bisect run cannot continue any more"
exit $res
fi
if [ $res -ne 0 ]; then
echo >&2 "bisect run failed:"
echo >&2 "'bisect_state $state' exited with error code $res"
exit $res
fi
if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
echo "bisect run success"
exit 0;
fi
done
}
bisect_log () {
test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting."
cat "$GIT_DIR/BISECT_LOG"
}
case "$#" in
0)
usage ;;
*)
cmd="$1"
shift
case "$cmd" in
help)
git bisect -h ;;
start)
bisect_start "$@" ;;
bad|good)
bisect_state "$cmd" "$@" ;;
skip)
bisect_skip "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
bisect_next "$@" ;;
visualize|view)
bisect_visualize "$@" ;;
reset)
bisect_reset "$@" ;;
replay)
bisect_replay "$@" ;;
log)
bisect_log ;;
run)
bisect_run "$@" ;;
*)
usage ;;
esac
esac

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1,8 @@
#!/bin/sh
if test "z$*" = zversion ||
test "z$*" = z--version
then
echo 'git-gui version 0.13.0.8.g8f85'
else
exec '/usr/local/git/share/git-gui/lib/Git Gui.app/Contents/MacOS/Wish' "$0" "$@"
fi

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1,456 @@
#!/usr/bin/perl
use lib (split(/:/, $ENV{GITPERLLIB} || "/usr/local/git/lib/perl5/site_perl"));
use 5.008;
use strict;
use warnings;
use Getopt::Std;
use File::Temp qw(tempdir);
use Data::Dumper;
use File::Basename qw(basename dirname);
use File::Spec;
use Git;
our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w, $opt_W, $opt_k);
getopts('uhPpvcfkam:d:w:W');
$opt_h && usage();
die "Need at least one commit identifier!" unless @ARGV;
# Get git-config settings
my $repo = Git->repository();
$opt_w = $repo->config('cvsexportcommit.cvsdir') unless defined $opt_w;
if ($opt_w || $opt_W) {
# Remember where GIT_DIR is before changing to CVS checkout
unless ($ENV{GIT_DIR}) {
# No GIT_DIR set. Figure it out for ourselves
my $gd =`git-rev-parse --git-dir`;
chomp($gd);
$ENV{GIT_DIR} = $gd;
}
# Make sure GIT_DIR is absolute
$ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR});
}
if ($opt_w) {
if (! -d $opt_w."/CVS" ) {
die "$opt_w is not a CVS checkout";
}
chdir $opt_w or die "Cannot change to CVS checkout at $opt_w";
}
unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
die "GIT_DIR is not defined or is unreadable";
}
my @cvs;
if ($opt_d) {
@cvs = ('cvs', '-d', $opt_d);
} else {
@cvs = ('cvs');
}
# resolve target commit
my $commit;
$commit = pop @ARGV;
$commit = safe_pipe_capture('git-rev-parse', '--verify', "$commit^0");
chomp $commit;
if ($?) {
die "The commit reference $commit did not resolve!";
}
# resolve what parent we want
my $parent;
if (@ARGV) {
$parent = pop @ARGV;
$parent = safe_pipe_capture('git-rev-parse', '--verify', "$parent^0");
chomp $parent;
if ($?) {
die "The parent reference did not resolve!";
}
}
# find parents from the commit itself
my @commit = safe_pipe_capture('git-cat-file', 'commit', $commit);
my @parents;
my $committer;
my $author;
my $stage = 'headers'; # headers, msg
my $title;
my $msg = '';
foreach my $line (@commit) {
chomp $line;
if ($stage eq 'headers' && $line eq '') {
$stage = 'msg';
next;
}
if ($stage eq 'headers') {
if ($line =~ m/^parent (\w{40})$/) { # found a parent
push @parents, $1;
} elsif ($line =~ m/^author (.+) \d+ [-+]\d+$/) {
$author = $1;
} elsif ($line =~ m/^committer (.+) \d+ [-+]\d+$/) {
$committer = $1;
}
} else {
$msg .= $line . "\n";
unless ($title) {
$title = $line;
}
}
}
my $noparent = "0000000000000000000000000000000000000000";
if ($parent) {
my $found;
# double check that it's a valid parent
foreach my $p (@parents) {
if ($p eq $parent) {
$found = 1;
last;
}; # found it
}
die "Did not find $parent in the parents for this commit!" if !$found and !$opt_P;
} else { # we don't have a parent from the cmdline...
if (@parents == 1) { # it's safe to get it from the commit
$parent = $parents[0];
} elsif (@parents == 0) { # there is no parent
$parent = $noparent;
} else { # cannot choose automatically from multiple parents
die "This commit has more than one parent -- please name the parent you want to use explicitly";
}
}
my $go_back_to = 0;
if ($opt_W) {
$opt_v && print "Resetting to $parent\n";
$go_back_to = `git symbolic-ref HEAD 2> /dev/null ||
git rev-parse HEAD` || die "Could not determine current branch";
system("git checkout -q $parent^0") && die "Could not check out $parent^0";
}
$opt_v && print "Applying to CVS commit $commit from parent $parent\n";
# grab the commit message
open(MSG, ">.msg") or die "Cannot open .msg for writing";
if ($opt_m) {
print MSG $opt_m;
}
print MSG $msg;
if ($opt_a) {
print MSG "\n\nAuthor: $author\n";
if ($author ne $committer) {
print MSG "Committer: $committer\n";
}
}
close MSG;
if ($parent eq $noparent) {
`git-diff-tree --binary -p --root $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
} else {
`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
}
## apply non-binary changes
# In pedantic mode require all lines of context to match. In normal
# mode, be compatible with diff/patch: assume 3 lines of context and
# require at least one line match, i.e. ignore at most 2 lines of
# context, like diff/patch do by default.
my $context = $opt_p ? '' : '-C1';
print "Checking if patch will apply\n";
my @stat;
open APPLY, "GIT_DIR= git-apply $context --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch";
@stat=<APPLY>;
close APPLY || die "Cannot patch";
my (@bfiles,@files,@afiles,@dfiles);
chomp @stat;
foreach (@stat) {
push (@bfiles,$1) if m/^-\t-\t(.*)$/;
push (@files, $1) if m/^-\t-\t(.*)$/;
push (@files, $1) if m/^\d+\t\d+\t(.*)$/;
push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/;
push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/;
}
map { s/^"(.*)"$/$1/g } @bfiles,@files;
map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files;
# check that the files are clean and up to date according to cvs
my $dirty;
my @dirs;
foreach my $p (@afiles) {
my $path = dirname $p;
while (!-d $path and ! grep { $_ eq $path } @dirs) {
unshift @dirs, $path;
$path = dirname $path;
}
}
# ... check dirs,
foreach my $d (@dirs) {
if (-e $d) {
$dirty = 1;
warn "$d exists and is not a directory!\n";
}
}
# ... query status of all files that we have a directory for and parse output of 'cvs status' to %cvsstat.
my @canstatusfiles;
foreach my $f (@files) {
my $path = dirname $f;
next if (grep { $_ eq $path } @dirs);
push @canstatusfiles, $f;
}
my %cvsstat;
if (@canstatusfiles) {
if ($opt_u) {
my @updated = xargs_safe_pipe_capture([@cvs, 'update'], @canstatusfiles);
print @updated;
}
# "cvs status" reorders the parameters, notably when there are multiple
# arguments with the same basename. So be precise here.
my %added = map { $_ => 1 } @afiles;
my %todo = map { $_ => 1 } @canstatusfiles;
while (%todo) {
my @canstatusfiles2 = ();
my %fullname = ();
foreach my $name (keys %todo) {
my $basename = basename($name);
# CVS reports files that don't exist in the current revision as
# "no file $basename" in its "status" output, so we should
# anticipate that. Totally unknown files will have a status
# "Unknown". However, if they exist in the Attic, their status
# will be "Up-to-date" (this means they were added once but have
# been removed).
$basename = "no file $basename" if $added{$basename};
$basename =~ s/^\s+//;
$basename =~ s/\s+$//;
if (!exists($fullname{$basename})) {
$fullname{$basename} = $name;
push (@canstatusfiles2, $name);
delete($todo{$name});
}
}
my @cvsoutput;
@cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2);
foreach my $l (@cvsoutput) {
chomp $l;
next unless
my ($file, $status) = $l =~ /^File:\s+(.*\S)\s+Status: (.*)$/;
my $fullname = $fullname{$file};
print STDERR "Huh? Status '$status' reported for unexpected file '$file'\n"
unless defined $fullname;
# This response means the file does not exist except in
# CVS's attic, so set the status accordingly
$status = "In-attic"
if $file =~ /^no file /
&& $status eq 'Up-to-date';
$cvsstat{$fullname{$file}} = $status
if defined $fullname{$file};
}
}
}
# ... Validate that new files have the correct status
foreach my $f (@afiles) {
next unless defined(my $stat = $cvsstat{$f});
# This means the file has never been seen before
next if $stat eq 'Unknown';
# This means the file has been seen before but was removed
next if $stat eq 'In-attic';
$dirty = 1;
warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
warn "Status was: $cvsstat{$f}\n";
}
# ... validate known files.
foreach my $f (@files) {
next if grep { $_ eq $f } @afiles;
# TODO:we need to handle removed in cvs
unless (defined ($cvsstat{$f}) and $cvsstat{$f} eq "Up-to-date") {
$dirty = 1;
warn "File $f not up to date but has status '$cvsstat{$f}' in your CVS checkout!\n";
}
# Depending on how your GIT tree got imported from CVS you may
# have a conflict between expanded keywords in your CVS tree and
# unexpanded keywords in the patch about to be applied.
if ($opt_k) {
my $orig_file ="$f.orig";
rename $f, $orig_file;
open(FILTER_IN, "<$orig_file") or die "Cannot open $orig_file\n";
open(FILTER_OUT, ">$f") or die "Cannot open $f\n";
while (<FILTER_IN>)
{
my $line = $_;
$line =~ s/\$([A-Z][a-z]+):[^\$]+\$/\$$1\$/g;
print FILTER_OUT $line;
}
close FILTER_IN;
close FILTER_OUT;
}
}
if ($dirty) {
if ($opt_f) { warn "The tree is not clean -- forced merge\n";
$dirty = 0;
} else {
die "Exiting: your CVS tree is not clean for this merge.";
}
}
print "Applying\n";
if ($opt_W) {
system("git checkout -q $commit^0") && die "cannot patch";
} else {
`GIT_DIR= git-apply $context --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch";
}
print "Patch applied successfully. Adding new files and directories to CVS\n";
my $dirtypatch = 0;
#
# We have to add the directories in order otherwise we will have
# problems when we try and add the sub-directory of a directory we
# have not added yet.
#
# Luckily this is easy to deal with by sorting the directories and
# dealing with the shortest ones first.
#
@dirs = sort { length $a <=> length $b} @dirs;
foreach my $d (@dirs) {
if (system(@cvs,'add',$d)) {
$dirtypatch = 1;
warn "Failed to cvs add directory $d -- you may need to do it manually";
}
}
foreach my $f (@afiles) {
if (grep { $_ eq $f } @bfiles) {
system(@cvs, 'add','-kb',$f);
} else {
system(@cvs, 'add', $f);
}
if ($?) {
$dirtypatch = 1;
warn "Failed to cvs add $f -- you may need to do it manually";
}
}
foreach my $f (@dfiles) {
system(@cvs, 'rm', '-f', $f);
if ($?) {
$dirtypatch = 1;
warn "Failed to cvs rm -f $f -- you may need to do it manually";
}
}
print "Commit to CVS\n";
print "Patch title (first comment line): $title\n";
my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files);
my $cmd = join(' ', @cvs)." commit -F .msg @commitfiles";
if ($dirtypatch) {
print "NOTE: One or more hunks failed to apply cleanly.\n";
print "You'll need to apply the patch in .cvsexportcommit.diff manually\n";
print "using a patch program. After applying the patch and resolving the\n";
print "problems you may commit using:";
print "\n cd \"$opt_w\"" if $opt_w;
print "\n $cmd\n";
print "\n git checkout $go_back_to\n" if $go_back_to;
print "\n";
exit(1);
}
if ($opt_c) {
print "Autocommit\n $cmd\n";
print xargs_safe_pipe_capture([@cvs, 'commit', '-F', '.msg'], @files);
if ($?) {
die "Exiting: The commit did not succeed";
}
print "Committed successfully to CVS\n";
# clean up
unlink(".msg");
} else {
print "Ready for you to commit, just run:\n\n $cmd\n";
}
# clean up
unlink(".cvsexportcommit.diff");
if ($opt_W) {
system("git checkout $go_back_to") && die "cannot move back to $go_back_to";
if (!($go_back_to =~ /^[0-9a-fA-F]{40}$/)) {
system("git symbolic-ref HEAD $go_back_to") &&
die "cannot move back to $go_back_to";
}
}
# CVS version 1.11.x and 1.12.x sleeps the wrong way to ensure the timestamp
# used by CVS and the one set by subsequence file modifications are different.
# If they are not different CVS will not detect changes.
sleep(1);
sub usage {
print STDERR <<END;
Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-k] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
END
exit(1);
}
# An alternative to `command` that allows input to be passed as an array
# to work around shell problems with weird characters in arguments
# if the exec returns non-zero we die
sub safe_pipe_capture {
my @output;
if (my $pid = open my $child, '-|') {
@output = (<$child>);
close $child or die join(' ',@_).": $! $?";
} else {
exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
}
return wantarray ? @output : join('',@output);
}
sub xargs_safe_pipe_capture {
my $MAX_ARG_LENGTH = 65536;
my $cmd = shift;
my @output;
my $output;
while(@_) {
my @args;
my $length = 0;
while(@_ && $length < $MAX_ARG_LENGTH) {
push @args, shift;
$length += length($args[$#args]);
}
if (wantarray) {
push @output, safe_pipe_capture(@$cmd, @args);
}
else {
$output .= safe_pipe_capture(@$cmd, @args);
}
}
return wantarray ? @output : $output;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1,122 @@
#!/usr/bin/perl
use lib (split(/:/, $ENV{GITPERLLIB} || "/usr/local/git/lib/perl5/site_perl"));
# Copyright (c) 2009, 2010 David Aguilar
#
# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
# git-difftool--helper script.
#
# This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL
# are exported for use by git-difftool--helper.
#
# Any arguments that are unknown to this script are forwarded to 'git diff'.
use 5.008;
use strict;
use warnings;
use Cwd qw(abs_path);
use File::Basename qw(dirname);
require Git;
my $DIR = abs_path(dirname($0));
sub usage
{
print << 'USAGE';
usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
[-y|--no-prompt] [-g|--gui]
['git diff' options]
USAGE
exit 1;
}
sub setup_environment
{
$ENV{PATH} = "$DIR:$ENV{PATH}";
$ENV{GIT_PAGER} = '';
$ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
}
sub exe
{
my $exe = shift;
if ($^O eq 'MSWin32' || $^O eq 'msys') {
return "$exe.exe";
}
return $exe;
}
sub generate_command
{
my @command = (exe('git'), 'diff');
my $skip_next = 0;
my $idx = -1;
my $prompt = '';
for my $arg (@ARGV) {
$idx++;
if ($skip_next) {
$skip_next = 0;
next;
}
if ($arg eq '-t' || $arg eq '--tool') {
usage() if $#ARGV <= $idx;
$ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
$skip_next = 1;
next;
}
if ($arg =~ /^--tool=/) {
$ENV{GIT_DIFF_TOOL} = substr($arg, 7);
next;
}
if ($arg eq '-x' || $arg eq '--extcmd') {
usage() if $#ARGV <= $idx;
$ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
$skip_next = 1;
next;
}
if ($arg =~ /^--extcmd=/) {
$ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
next;
}
if ($arg eq '-g' || $arg eq '--gui') {
eval {
my $tool = Git::command_oneline('config',
'diff.guitool');
if (length($tool)) {
$ENV{GIT_DIFF_TOOL} = $tool;
}
};
next;
}
if ($arg eq '-y' || $arg eq '--no-prompt') {
$prompt = 'no';
next;
}
if ($arg eq '--prompt') {
$prompt = 'yes';
next;
}
if ($arg eq '-h' || $arg eq '--help') {
usage();
}
push @command, $arg;
}
if ($prompt eq 'yes') {
$ENV{GIT_DIFFTOOL_PROMPT} = 'true';
} elsif ($prompt eq 'no') {
$ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
}
return @command
}
setup_environment();
# ActiveState Perl for Win32 does not implement POSIX semantics of
# exec* system call. It just spawns the given executable and finishes
# the starting program, exiting with code 0.
# system will at least catch the errors returned by git diff,
# allowing the caller of git difftool better handling of failures.
my $rc = system(generate_command());
exit($rc | ($rc >> 8));

View file

@ -0,0 +1,72 @@
#!/bin/sh
# git-difftool--helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
# This script is typically launched by using the 'git difftool'
# convenience command.
#
# Copyright (c) 2009, 2010 David Aguilar
TOOL_MODE=diff
. git-mergetool--lib
# difftool.prompt controls the default prompt/no-prompt behavior
# and is overridden with $GIT_DIFFTOOL*_PROMPT.
should_prompt () {
prompt_merge=$(git config --bool mergetool.prompt || echo true)
prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
if test "$prompt" = true; then
test -z "$GIT_DIFFTOOL_NO_PROMPT"
else
test -n "$GIT_DIFFTOOL_PROMPT"
fi
}
# Indicates that --extcmd=... was specified
use_ext_cmd () {
test -n "$GIT_DIFFTOOL_EXTCMD"
}
launch_merge_tool () {
# Merged is the filename as it appears in the work tree
# Local is the contents of a/filename
# Remote is the contents of b/filename
# Custom merge tool commands might use $BASE so we provide it
MERGED="$1"
LOCAL="$2"
REMOTE="$3"
BASE="$1"
# $LOCAL and $REMOTE are temporary files so prompt
# the user with the real $MERGED name before launching $merge_tool.
if should_prompt; then
printf "\nViewing: '$MERGED'\n"
if use_ext_cmd; then
printf "Hit return to launch '%s': " \
"$GIT_DIFFTOOL_EXTCMD"
else
printf "Hit return to launch '%s': " "$merge_tool"
fi
read ans
fi
if use_ext_cmd; then
export BASE
eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
else
run_merge_tool "$merge_tool"
fi
}
if ! use_ext_cmd; then
if test -n "$GIT_DIFF_TOOL"; then
merge_tool="$GIT_DIFF_TOOL"
else
merge_tool="$(get_merge_tool)" || exit
fi
fi
# Launch the merge tool on each path provided by 'git diff'
while test $# -gt 6
do
launch_merge_tool "$1" "$2" "$5"
shift 7
done

View file

@ -0,0 +1 @@
../../bin/git

Binary file not shown.

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1,516 @@
#!/bin/sh
#
# Rewrite revision history
# Copyright (c) Petr Baudis, 2006
# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
#
# Lets you rewrite the revision history of the current branch, creating
# a new branch. You can specify a number of filters to modify the commits,
# files and trees.
# The following functions will also be available in the commit filter:
functions=$(cat << \EOF
warn () {
echo "$*" >&2
}
map()
{
# if it was not rewritten, take the original
if test -r "$workdir/../map/$1"
then
cat "$workdir/../map/$1"
else
echo "$1"
fi
}
# if you run 'skip_commit "$@"' in a commit filter, it will print
# the (mapped) parents, effectively skipping the commit.
skip_commit()
{
shift;
while [ -n "$1" ];
do
shift;
map "$1";
shift;
done;
}
# if you run 'git_commit_non_empty_tree "$@"' in a commit filter,
# it will skip commits that leave the tree untouched, commit the other.
git_commit_non_empty_tree()
{
if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
map "$3"
else
git commit-tree "$@"
fi
}
# override die(): this version puts in an extra line break, so that
# the progress is still visible
die()
{
echo >&2
echo "$*" >&2
exit 1
}
EOF
)
eval "$functions"
# When piped a commit, output a script to set the ident of either
# "author" or "committer
set_ident () {
lid="$(echo "$1" | tr "[A-Z]" "[a-z]")"
uid="$(echo "$1" | tr "[a-z]" "[A-Z]")"
pick_id_script='
/^'$lid' /{
s/'\''/'\''\\'\'\''/g
h
s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
s/'\''/'\''\'\'\''/g
s/.*/GIT_'$uid'_NAME='\''&'\''; export GIT_'$uid'_NAME/p
g
s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
s/'\''/'\''\'\'\''/g
s/.*/GIT_'$uid'_EMAIL='\''&'\''; export GIT_'$uid'_EMAIL/p
g
s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
s/'\''/'\''\'\'\''/g
s/.*/GIT_'$uid'_DATE='\''&'\''; export GIT_'$uid'_DATE/p
q
}
'
LANG=C LC_ALL=C sed -ne "$pick_id_script"
# Ensure non-empty id name.
echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac"
}
USAGE="[--env-filter <command>] [--tree-filter <command>]
[--index-filter <command>] [--parent-filter <command>]
[--msg-filter <command>] [--commit-filter <command>]
[--tag-name-filter <command>] [--subdirectory-filter <directory>]
[--original <namespace>] [-d <directory>] [-f | --force]
[<rev-list options>...]"
OPTIONS_SPEC=
. git-sh-setup
if [ "$(is_bare_repository)" = false ]; then
git diff-files --ignore-submodules --quiet &&
git diff-index --cached --quiet HEAD -- ||
die "Cannot rewrite branch(es) with a dirty working directory."
fi
tempdir=.git-rewrite
filter_env=
filter_tree=
filter_index=
filter_parent=
filter_msg=cat
filter_commit=
filter_tag_name=
filter_subdir=
orig_namespace=refs/original/
force=
prune_empty=
remap_to_ancestor=
while :
do
case "$1" in
--)
shift
break
;;
--force|-f)
shift
force=t
continue
;;
--remap-to-ancestor)
# deprecated ($remap_to_ancestor is set now automatically)
shift
remap_to_ancestor=t
continue
;;
--prune-empty)
shift
prune_empty=t
continue
;;
-*)
;;
*)
break;
esac
# all switches take one argument
ARG="$1"
case "$#" in 1) usage ;; esac
shift
OPTARG="$1"
shift
case "$ARG" in
-d)
tempdir="$OPTARG"
;;
--env-filter)
filter_env="$OPTARG"
;;
--tree-filter)
filter_tree="$OPTARG"
;;
--index-filter)
filter_index="$OPTARG"
;;
--parent-filter)
filter_parent="$OPTARG"
;;
--msg-filter)
filter_msg="$OPTARG"
;;
--commit-filter)
filter_commit="$functions; $OPTARG"
;;
--tag-name-filter)
filter_tag_name="$OPTARG"
;;
--subdirectory-filter)
filter_subdir="$OPTARG"
remap_to_ancestor=t
;;
--original)
orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
;;
*)
usage
;;
esac
done
case "$prune_empty,$filter_commit" in
,)
filter_commit='git commit-tree "$@"';;
t,)
filter_commit="$functions;"' git_commit_non_empty_tree "$@"';;
,*)
;;
*)
die "Cannot set --prune-empty and --commit-filter at the same time"
esac
case "$force" in
t)
rm -rf "$tempdir"
;;
'')
test -d "$tempdir" &&
die "$tempdir already exists, please remove it"
esac
mkdir -p "$tempdir/t" &&
tempdir="$(cd "$tempdir"; pwd)" &&
cd "$tempdir/t" &&
workdir="$(pwd)" ||
die ""
# Remove tempdir on exit
trap 'cd ../..; rm -rf "$tempdir"' 0
ORIG_GIT_DIR="$GIT_DIR"
ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
GIT_WORK_TREE=.
export GIT_DIR GIT_WORK_TREE
# Make sure refs/original is empty
git for-each-ref > "$tempdir"/backup-refs || exit
while read sha1 type name
do
case "$force,$name" in
,$orig_namespace*)
die "Cannot create a new backup.
A previous backup already exists in $orig_namespace
Force overwriting the backup with -f"
;;
t,$orig_namespace*)
git update-ref -d "$name" $sha1
;;
esac
done < "$tempdir"/backup-refs
# The refs should be updated if their heads were rewritten
git rev-parse --no-flags --revs-only --symbolic-full-name \
--default HEAD "$@" > "$tempdir"/raw-heads || exit
sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
test -s "$tempdir"/heads ||
die "Which ref do you want to rewrite?"
GIT_INDEX_FILE="$(pwd)/../index"
export GIT_INDEX_FILE
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"
# we need "--" only if there are no path arguments in $@
nonrevs=$(git rev-parse --no-revs "$@") || exit
if test -z "$nonrevs"
then
dashdash=--
else
dashdash=
remap_to_ancestor=t
fi
rev_args=$(git rev-parse --revs-only "$@")
case "$filter_subdir" in
"")
eval set -- "$(git rev-parse --sq --no-revs "$@")"
;;
*)
eval set -- "$(git rev-parse --sq --no-revs "$@" $dashdash \
"$filter_subdir")"
;;
esac
git rev-list --reverse --topo-order --default HEAD \
--parents --simplify-merges $rev_args "$@" > ../revs ||
die "Could not get the commits"
commits=$(wc -l <../revs | tr -d " ")
test $commits -eq 0 && die "Found nothing to rewrite"
# Rewrite the commits
git_filter_branch__commit_count=0
while read commit parents; do
git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1))
printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)"
case "$filter_subdir" in
"")
git read-tree -i -m $commit
;;
*)
# The commit may not have the subdirectory at all
err=$(git read-tree -i -m $commit:"$filter_subdir" 2>&1) || {
if ! git rev-parse -q --verify $commit:"$filter_subdir"
then
rm -f "$GIT_INDEX_FILE"
else
echo >&2 "$err"
false
fi
}
esac || die "Could not initialize the index"
GIT_COMMIT=$commit
export GIT_COMMIT
git cat-file commit "$commit" >../commit ||
die "Cannot read commit $commit"
eval "$(set_ident AUTHOR <../commit)" ||
die "setting author failed for commit $commit"
eval "$(set_ident COMMITTER <../commit)" ||
die "setting committer failed for commit $commit"
eval "$filter_env" < /dev/null ||
die "env filter failed: $filter_env"
if [ "$filter_tree" ]; then
git checkout-index -f -u -a ||
die "Could not checkout the index"
# files that $commit removed are now still in the working tree;
# remove them, else they would be added again
git clean -d -q -f -x
eval "$filter_tree" < /dev/null ||
die "tree filter failed: $filter_tree"
(
git diff-index -r --name-only --ignore-submodules $commit &&
git ls-files --others
) > "$tempdir"/tree-state || exit
git update-index --add --replace --remove --stdin \
< "$tempdir"/tree-state || exit
fi
eval "$filter_index" < /dev/null ||
die "index filter failed: $filter_index"
parentstr=
for parent in $parents; do
for reparent in $(map "$parent"); do
parentstr="$parentstr -p $reparent"
done
done
if [ "$filter_parent" ]; then
parentstr="$(echo "$parentstr" | eval "$filter_parent")" ||
die "parent filter failed: $filter_parent"
fi
sed -e '1,/^$/d' <../commit | \
eval "$filter_msg" > ../message ||
die "msg filter failed: $filter_msg"
/bin/sh -c "$filter_commit" "git commit-tree" \
$(git write-tree) $parentstr < ../message > ../map/$commit ||
die "could not write rewritten commit"
done <../revs
# If we are filtering for paths, as in the case of a subdirectory
# filter, it is possible that a specified head is not in the set of
# rewritten commits, because it was pruned by the revision walker.
# Ancestor remapping fixes this by mapping these heads to the unique
# nearest ancestor that survived the pruning.
if test "$remap_to_ancestor" = t
then
while read ref
do
sha1=$(git rev-parse "$ref"^0)
test -f "$workdir"/../map/$sha1 && continue
ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@")
test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
done < "$tempdir"/heads
fi
# Finally update the refs
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
echo
while read ref
do
# avoid rewriting a ref twice
test -f "$orig_namespace$ref" && continue
sha1=$(git rev-parse "$ref"^0)
rewritten=$(map $sha1)
test $sha1 = "$rewritten" &&
warn "WARNING: Ref '$ref' is unchanged" &&
continue
case "$rewritten" in
'')
echo "Ref '$ref' was deleted"
git update-ref -m "filter-branch: delete" -d "$ref" $sha1 ||
die "Could not delete $ref"
;;
$_x40)
echo "Ref '$ref' was rewritten"
if ! git update-ref -m "filter-branch: rewrite" \
"$ref" $rewritten $sha1 2>/dev/null; then
if test $(git cat-file -t "$ref") = tag; then
if test -z "$filter_tag_name"; then
warn "WARNING: You said to rewrite tagged commits, but not the corresponding tag."
warn "WARNING: Perhaps use '--tag-name-filter cat' to rewrite the tag."
fi
else
die "Could not rewrite $ref"
fi
fi
;;
*)
# NEEDSWORK: possibly add -Werror, making this an error
warn "WARNING: '$ref' was rewritten into multiple commits:"
warn "$rewritten"
warn "WARNING: Ref '$ref' points to the first one now."
rewritten=$(echo "$rewritten" | head -n 1)
git update-ref -m "filter-branch: rewrite to first" \
"$ref" $rewritten $sha1 ||
die "Could not rewrite $ref"
;;
esac
git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
exit
done < "$tempdir"/heads
# TODO: This should possibly go, with the semantics that all positive given
# refs are updated, and their original heads stored in refs/original/
# Filter tags
if [ "$filter_tag_name" ]; then
git for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
while read sha1 type ref; do
ref="${ref#refs/tags/}"
# XXX: Rewrite tagged trees as well?
if [ "$type" != "commit" -a "$type" != "tag" ]; then
continue;
fi
if [ "$type" = "tag" ]; then
# Dereference to a commit
sha1t="$sha1"
sha1="$(git rev-parse -q "$sha1"^{commit})" || continue
fi
[ -f "../map/$sha1" ] || continue
new_sha1="$(cat "../map/$sha1")"
GIT_COMMIT="$sha1"
export GIT_COMMIT
new_ref="$(echo "$ref" | eval "$filter_tag_name")" ||
die "tag name filter failed: $filter_tag_name"
echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
if [ "$type" = "tag" ]; then
new_sha1=$( ( printf 'object %s\ntype commit\ntag %s\n' \
"$new_sha1" "$new_ref"
git cat-file tag "$ref" |
sed -n \
-e '1,/^$/{
/^object /d
/^type /d
/^tag /d
}' \
-e '/^-----BEGIN PGP SIGNATURE-----/q' \
-e 'p' ) |
git mktag) ||
die "Could not create new tag object for $ref"
if git cat-file tag "$ref" | \
sane_grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1
then
warn "gpg signature stripped from tag object $sha1t"
fi
fi
git update-ref "refs/tags/$new_ref" "$new_sha1" ||
die "Could not write tag $new_ref"
done
fi
cd ../..
rm -rf "$tempdir"
trap - 0
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
test -z "$ORIG_GIT_DIR" || {
GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
}
test -z "$ORIG_GIT_WORK_TREE" || {
GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
export GIT_WORK_TREE
}
test -z "$ORIG_GIT_INDEX_FILE" || {
GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
export GIT_INDEX_FILE
}
if [ "$(is_bare_repository)" = false ]; then
git read-tree -u -m HEAD || exit
fi
exit 0

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1,8 @@
#!/bin/sh
if test "z$*" = zversion ||
test "z$*" = z--version
then
echo 'git-gui version 0.13.0.8.g8f85'
else
exec '/usr/local/git/share/git-gui/lib/Git Gui.app/Contents/MacOS/Wish' "$0" "$@"
fi

View file

@ -0,0 +1,66 @@
#!/bin/sh
# Tcl ignores the next line -*- tcl -*- \
exec wish "$0" -- "$@"
# This is a trivial implementation of an SSH_ASKPASS handler.
# Git-gui uses this script if none are already configured.
package require Tk
set answer {}
set yesno 0
set rc 255
if {$argc < 1} {
set prompt "Enter your OpenSSH passphrase:"
} else {
set prompt [join $argv " "]
if {[regexp -nocase {\(yes\/no\)\?\s*$} $prompt]} {
set yesno 1
}
}
message .m -text $prompt -justify center -aspect 4000
pack .m -side top -fill x -padx 20 -pady 20 -expand 1
entry .e -textvariable answer -width 50
pack .e -side top -fill x -padx 10 -pady 10
if {!$yesno} {
.e configure -show "*"
}
frame .b
button .b.ok -text OK -command finish
button .b.cancel -text Cancel -command cancel
pack .b.ok -side left -expand 1
pack .b.cancel -side right -expand 1
pack .b -side bottom -fill x -padx 10 -pady 10
bind . <Visibility> {focus -force .e}
bind . <Key-Return> [list .b.ok invoke]
bind . <Key-Escape> [list .b.cancel invoke]
bind . <Destroy> {set rc $rc}
proc cancel {} {
set ::rc 255
}
proc finish {} {
if {$::yesno} {
if {$::answer ne "yes" && $::answer ne "no"} {
tk_messageBox -icon error -title "Error" -type ok \
-message "Only 'yes' or 'no' input allowed."
return
}
}
puts $::answer
set ::rc 0
}
wm title . "OpenSSH"
tk::PlaceWindow .
vwait rc
exit $rc

View file

@ -0,0 +1 @@
../../bin/git

View file

@ -0,0 +1 @@
../../bin/git

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more