nixenvironment 0.0.59 → 0.0.60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +14 -14
  4. data/bin/Config +2 -2
  5. data/bin/nixenvironment +420 -842
  6. data/legacy/CleanWorkingCopy.sh +69 -0
  7. data/legacy/Deploy.sh +44 -0
  8. data/legacy/DeployAPK.py +58 -0
  9. data/legacy/DeployIPA.sh +125 -0
  10. data/legacy/DetectSCM.sh +11 -0
  11. data/legacy/GenerateCodeCoverageForXCTests.sh +134 -0
  12. data/legacy/GenerateCodeDuplicationReport.sh +24 -0
  13. data/legacy/IncrementBuildNumber.py +129 -0
  14. data/legacy/LoadBuildEnvVars.sh +116 -0
  15. data/legacy/MakeTag.sh +94 -0
  16. data/legacy/RemoveTemporaryFiles.sh +9 -0
  17. data/legacy/SaveRevision.sh +122 -0
  18. data/legacy/UnityBuildAndroid.py +84 -0
  19. data/legacy/UnityBuildAutomationScripts/CommandLineReader.cs +130 -0
  20. data/legacy/UnityBuildAutomationScripts/NIXBuilder.cs +105 -0
  21. data/legacy/UnityBuildEnvVars.py +41 -0
  22. data/legacy/VerifyBinarySigning.py +80 -0
  23. data/legacy/svn-clean.pl +246 -0
  24. data/legacy/svncopy.pl +1134 -0
  25. data/lib/nixenvironment.rb +5 -0
  26. data/lib/nixenvironment/archiver.rb +690 -0
  27. data/lib/nixenvironment/build_env_vars_loader.rb +24 -0
  28. data/lib/nixenvironment/cmd_executor.rb +33 -0
  29. data/lib/nixenvironment/config.rb +127 -14
  30. data/lib/nixenvironment/git.rb +107 -0
  31. data/lib/nixenvironment/plist.rb +52 -0
  32. data/lib/nixenvironment/version.rb +1 -1
  33. data/lib/nixenvironment/xcodebuild.rb +167 -0
  34. data/nixenvironment.gemspec +6 -0
  35. data/utils/XcodeIconTagger/IconTagger +0 -0
  36. data/utils/XcodeIconTagger/masks/OneLineMask.png +0 -0
  37. data/utils/XcodeIconTagger/masks/TwoLineMask.png +0 -0
  38. data/utils/aapt +0 -0
  39. data/utils/gcovr +986 -0
  40. data/utils/identitieslist +0 -0
  41. data/utils/simian-2.3.33.jar +0 -0
  42. metadata +118 -2
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/python
2
+
3
+ # Created by Egor Zubkov on 16/12/14.
4
+ # Copyright 2014 NIX. All rights reserved.
5
+
6
+ import argparse
7
+ import os
8
+ import sys
9
+ import UnityBuildEnvVars
10
+
11
+ default_configuration = "Debug"
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="Script builds Android build in Unity project.")
15
+ parser.add_argument("--configuration", default=default_configuration)
16
+ parser.add_argument("--unity-path", required=True)
17
+ parser.add_argument("--development-build", action="store_true")
18
+ parser.add_argument("--keystore-path")
19
+ parser.add_argument("--keystore-password")
20
+ parser.add_argument("--key-alias-name")
21
+ parser.add_argument("--key-alias-password")
22
+ args = parser.parse_args()
23
+
24
+ if not args.unity_path:
25
+ print "ERROR: unity_path must not be empty string!"
26
+ exit(1)
27
+
28
+ development_build_arg = args.development_build
29
+ configuration_arg = args.configuration
30
+
31
+ if configuration_arg != default_configuration:
32
+ configuration_arg = "Release"
33
+ development_build_arg = None
34
+
35
+ if not args.keystore_path or not os.path.exists(args.keystore_path):
36
+ print "ERROR: KeyStore file must exist!"
37
+ exit(1)
38
+
39
+ revision_number = UnityBuildEnvVars.revision_number()
40
+
41
+ build_path = build_apk(revision_number, args.unity_path, development_build_arg, args.keystore_path, args.keystore_password, args.key_alias_name, args.key_alias_password)
42
+ UnityBuildEnvVars.save_env_vars(configuration_arg, build_path)
43
+
44
+ def build_apk(bundle_version, unity_path, development_build, keystore_path, keystore_password, key_alias_name, key_alias_password):
45
+ build_path = os.path.join(os.getcwd(), "Builds/Android/Build.apk")
46
+ build_path_arg = "buildPath=%s" % build_path
47
+ bundle_version_arg = ";bundleVersionCode=%s" % bundle_version
48
+
49
+ development_build_arg = ""
50
+
51
+ if development_build == True:
52
+ development_build_arg = ";developmentBuild="
53
+
54
+ keystore_path_arg = ""
55
+
56
+ if keystore_path:
57
+ keystore_path_arg = ";keyStorePath=%s" % keystore_path
58
+
59
+ keystore_password_arg = ""
60
+
61
+ if keystore_password:
62
+ keystore_password_arg = ";keyStorePassword=%s" % keystore_password
63
+
64
+ key_alias_name_arg = ""
65
+
66
+ if key_alias_name:
67
+ key_alias_name_arg = ";keyAliasName=%s" % key_alias_name
68
+
69
+ key_alias_password_arg = ""
70
+
71
+ if key_alias_password:
72
+ key_alias_password_arg = ";keyAliasPassword=%s" % key_alias_password
73
+
74
+ args = "'{0}' -projectPath '{1}' -executeMethod NIXBuilder.MakeAndroidBuild -batchmode -logFile -quit -customArgs:\"{2}{3}{4}{5}{6}{7}{8}\"".format(unity_path, os.getcwd(), build_path_arg, bundle_version_arg, development_build_arg, keystore_path_arg, keystore_password_arg, key_alias_name_arg, key_alias_password_arg)
75
+ success = os.system(args)
76
+
77
+ if success != 0:
78
+ print "ERROR: build_apk failed!"
79
+ exit(1)
80
+
81
+ return build_path
82
+
83
+ if __name__ == '__main__':
84
+ sys.exit(main())
@@ -0,0 +1,130 @@
1
+ #region Author
2
+ /************************************************************************************************************
3
+ Author: EpixCode (Keven Poulin)
4
+ Website: http://www.EpixCode.com
5
+ GitHub: https://github.com/EpixCode
6
+ Twitter: https://twitter.com/EpixCode (@EpixCode)
7
+ LinkedIn: http://www.linkedin.com/in/kevenpoulin
8
+ ************************************************************************************************************/
9
+ #endregion
10
+
11
+ #region Copyright
12
+ /************************************************************************************************************
13
+ Copyright (C) 2013 EpixCode
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
16
+ and associated documentation files (the "Software"), to deal in the Software without restriction,
17
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
18
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
19
+ to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all copies or substantial
22
+ portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
25
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
27
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
+ ************************************************************************************************************/
30
+ #endregion
31
+
32
+ #region Class Documentation
33
+ /************************************************************************************************************
34
+ Class Name: CommandLineReader.cs
35
+ Namespace: Com.EpixCode.Util
36
+ Type: Util, Static
37
+ Definition:
38
+ CommandLineReader.cs give the ability to access [Custom Arguments] sent
39
+ through the command line. Simply add your custom arguments under the
40
+ keyword '-CustomArgs:' and seperate them by ';'.
41
+ Example:
42
+ C:\Program Files (x86)\Unity\Editor\Unity.exe [ProjectLocation] -executeMethod [Your entrypoint] -quit -CustomArgs:Language=en_US;Version=1.02
43
+
44
+ ************************************************************************************************************/
45
+ #endregion
46
+
47
+ #region Using
48
+ using System;
49
+ using System.Collections.Generic;
50
+ using System.Linq;
51
+ using UnityEngine;
52
+ #endregion
53
+
54
+ public class CommandLineReader
55
+ {
56
+ //Config
57
+ private const string CUSTOM_ARGS_PREFIX = "-customArgs:";
58
+ private const char CUSTOM_ARGS_SEPARATOR = ';';
59
+
60
+ public static string[] GetCommandLineArgs()
61
+ {
62
+ return Environment.GetCommandLineArgs();
63
+ }
64
+
65
+ public static string GetCommandLine()
66
+ {
67
+ string[] args = GetCommandLineArgs();
68
+
69
+ if (args.Length > 0)
70
+ {
71
+ return string.Join(" ", args);
72
+ }
73
+ else
74
+ {
75
+ Debug.LogError("CommandLineReader.cs - GetCommandLine() - Can't find any command line arguments!");
76
+ return "";
77
+ }
78
+ }
79
+
80
+ public static Dictionary<string,string> GetCustomArguments()
81
+ {
82
+ Dictionary<string, string> customArgsDict = new Dictionary<string, string>();
83
+ string[] commandLineArgs = GetCommandLineArgs();
84
+ string[] customArgs;
85
+ string[] customArgBuffer;
86
+ string customArgsStr = "";
87
+
88
+ try
89
+ {
90
+ customArgsStr = commandLineArgs.Where(row => row.Contains(CUSTOM_ARGS_PREFIX)).Single();
91
+ }
92
+ catch (Exception e)
93
+ {
94
+ Debug.LogError("CommandLineReader.cs - GetCustomArguments() - Can't retrieve any custom arguments in the command line [" + commandLineArgs + "]. Exception: " + e);
95
+ return customArgsDict;
96
+ }
97
+
98
+ customArgsStr = customArgsStr.Replace(CUSTOM_ARGS_PREFIX, "");
99
+ customArgs = customArgsStr.Split(CUSTOM_ARGS_SEPARATOR);
100
+
101
+ foreach (string customArg in customArgs)
102
+ {
103
+ customArgBuffer = customArg.Split('=');
104
+ if (customArgBuffer.Length == 2)
105
+ {
106
+ customArgsDict.Add(customArgBuffer[0], customArgBuffer[1]);
107
+ }
108
+ else
109
+ {
110
+ Debug.LogWarning("CommandLineReader.cs - GetCustomArguments() - The custom argument [" + customArg + "] seem to be malformed.");
111
+ }
112
+ }
113
+
114
+ return customArgsDict;
115
+ }
116
+
117
+ public static string GetCustomArgument(string argumentName)
118
+ {
119
+ Dictionary<string, string> customArgsDict = GetCustomArguments();
120
+
121
+ if (customArgsDict.ContainsKey(argumentName))
122
+ {
123
+ return customArgsDict[argumentName];
124
+ }
125
+ else
126
+ {
127
+ return null;
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,105 @@
1
+ /*
2
+ NIXBuilder.cs
3
+ Automatically changes the target platform and creates a build.
4
+
5
+ Installation
6
+ Place in an Editor folder.
7
+ */
8
+
9
+ using System;
10
+ using System.IO;
11
+ using UnityEditor;
12
+
13
+ public static class NIXBuilder
14
+ {
15
+ private const string BundleVersionCodeArgument = "bundleVersionCode";
16
+
17
+ private const string BuildPathArgument = "buildPath";
18
+
19
+ private const string DevelopmentBuildArgument = "developmentBuild";
20
+
21
+ private const string KeyStorePathArgument = "keyStorePath";
22
+
23
+ private const string KeyStorePasswordArgument = "keyStorePassword";
24
+
25
+ private const string KeyAliasNameArgument = "keyAliasName";
26
+
27
+ private const string KeyAliasPasswordArgument = "keyAliasPassword";
28
+
29
+ public static string[] GetScenePaths()
30
+ {
31
+ string[] scenes = new string[EditorBuildSettings.scenes.Length];
32
+
33
+ for (int i = 0; i < scenes.Length; i++)
34
+ {
35
+ scenes[i] = EditorBuildSettings.scenes[i].path;
36
+ }
37
+
38
+ return scenes;
39
+ }
40
+
41
+ public static void MakeiOSBuild()
42
+ {
43
+ string projectPath = CommandLineReader.GetCustomArgument(BuildPathArgument);
44
+ CreateFolder(projectPath);
45
+
46
+ BuildTarget buildTarget;
47
+
48
+ #if UNITY_4_6
49
+ buildTarget = BuildTarget.iPhone;
50
+ #else
51
+ buildTarget = BuildTarget.iOS;
52
+ #endif
53
+
54
+ EditorUserBuildSettings.SwitchActiveBuildTarget(buildTarget);
55
+ BuildPipeline.BuildPlayer(GetScenePaths(), projectPath, buildTarget, GetBuildOptions());
56
+ }
57
+
58
+ public static void MakeAndroidBuild()
59
+ {
60
+ string buildPath = CommandLineReader.GetCustomArgument(BuildPathArgument);
61
+ CreateFolder(Path.GetDirectoryName(buildPath));
62
+
63
+ var keyStorePath = CommandLineReader.GetCustomArgument(KeyStorePathArgument);
64
+
65
+ PlayerSettings.Android.bundleVersionCode = Convert.ToInt32(CommandLineReader.GetCustomArgument(BundleVersionCodeArgument));
66
+
67
+ if (File.Exists(keyStorePath))
68
+ {
69
+ PlayerSettings.Android.keystoreName = keyStorePath;
70
+ PlayerSettings.Android.keystorePass = CommandLineReader.GetCustomArgument(KeyStorePasswordArgument);
71
+ PlayerSettings.Android.keyaliasName = CommandLineReader.GetCustomArgument(KeyAliasNameArgument);
72
+ PlayerSettings.Android.keyaliasPass = CommandLineReader.GetCustomArgument(KeyAliasPasswordArgument);
73
+ }
74
+ else
75
+ {
76
+ PlayerSettings.Android.keystoreName = null;
77
+ PlayerSettings.Android.keystorePass = null;
78
+ PlayerSettings.Android.keyaliasName = null;
79
+ PlayerSettings.Android.keyaliasPass = null;
80
+ }
81
+
82
+ EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
83
+ BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, BuildTarget.Android, GetBuildOptions());
84
+ }
85
+
86
+ public static void CreateFolder(string thePath)
87
+ {
88
+ if (!Directory.Exists(thePath))
89
+ {
90
+ Directory.CreateDirectory(thePath);
91
+ }
92
+ }
93
+
94
+ public static BuildOptions GetBuildOptions()
95
+ {
96
+ BuildOptions buildOptions = BuildOptions.None;
97
+
98
+ if (CommandLineReader.GetCustomArgument(DevelopmentBuildArgument) != null)
99
+ {
100
+ buildOptions |= BuildOptions.Development;
101
+ }
102
+
103
+ return buildOptions;
104
+ }
105
+ }
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/python
2
+
3
+ # Created by Egor Zubkov on 17/12/14.
4
+ # Copyright 2014 NIX. All rights reserved.
5
+
6
+ import os
7
+ import plistlib
8
+
9
+ last_revision_filename = "_last_revision.sh"
10
+ unity_last_build_vars_filename = "_unity_last_build_vars.plist"
11
+
12
+ monotomic_revision_var = "$MONOTONIC_REVISION"
13
+ working_copy_is_clean_var = "$WORKING_COPY_IS_CLEAN"
14
+
15
+ configuration_key = "Configuration"
16
+ revision_number_key = "RevisionNumber"
17
+ build_path_key = "BuildPath"
18
+
19
+
20
+ def revision_number():
21
+ command = os.popen("source %s && echo %s" % (last_revision_filename, monotomic_revision_var))
22
+ revision_number = command.read().strip()
23
+
24
+ if revision_number == '':
25
+ exit(1)
26
+
27
+ return revision_number
28
+
29
+
30
+ def save_env_vars(configuration, build_path):
31
+ plist = { configuration_key : configuration, revision_number_key : revision_number(), build_path_key : build_path }
32
+
33
+ last_build_vars = os.path.join(os.getcwd(), unity_last_build_vars_filename)
34
+ plistlib.writePlist(plist, last_build_vars)
35
+
36
+
37
+ def env_vars():
38
+ last_build_vars_path = os.path.join(os.getcwd(), unity_last_build_vars_filename)
39
+ last_build_vars = plistlib.readPlist(last_build_vars_path)
40
+ return last_build_vars[configuration_key], last_build_vars[revision_number_key], last_build_vars[build_path_key]
41
+
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/python
2
+
3
+ import os
4
+ import optparse
5
+ import sys
6
+
7
+ def main():
8
+ parser = optparse.OptionParser(description='Verifies that iOS binary has expected keychain-access-group. Using the same keychain-access-group insures\
9
+ that app will have access to its keychain after updates.',
10
+ prog='VerifyBinarySigning')
11
+
12
+ parser.add_option("-b", "--binary", dest="path_to_binary", help="Full path to app binary", metavar="BINARY")
13
+ parser.add_option("-s", "--keychain_access_group", dest="keychain_access_group", help="Expected keychain-access-group", metavar="KEYCHAIN-ACCESS-GROUP")
14
+
15
+ (options, args) = parser.parse_args()
16
+
17
+ if options.path_to_binary is None or options.keychain_access_group is None:
18
+ parser.error('All arguments must be specified. Use option -h to see the usage.')
19
+
20
+ logfile = open(options.path_to_binary, "r").readlines()
21
+
22
+ currentLineNumber = 0
23
+
24
+ """
25
+ example from the binary:
26
+
27
+ .
28
+ .
29
+ .
30
+ <key>keychain-access-groups</key>
31
+ <array>
32
+ <string>KEYCHAIN-ACCESS-GROUP</string>
33
+ </array>
34
+ .
35
+ .
36
+ .
37
+ """
38
+
39
+ for line in logfile:
40
+ if line.find('keychain-access-groups') != -1:
41
+ currentLineNumber += 1
42
+
43
+ if logfile[currentLineNumber].strip() != "<array>":
44
+ print_error("Something wrong happened, there is no <array> in the <key>keychain-access-groups</key>")
45
+ return 1
46
+
47
+ currentLineNumber += 1
48
+
49
+ while logfile[currentLineNumber].strip() != "</array>":
50
+ line_with_access_group = logfile[currentLineNumber].strip().replace("<string>","").replace("</string>","")
51
+
52
+ print line_with_access_group
53
+
54
+ if line_with_access_group == options.keychain_access_group:
55
+ print 'App is signed correctly'
56
+ return 0
57
+
58
+ currentLineNumber += 1
59
+
60
+ print_error("App is signed incorrectly, specified keychain access group '%s' was not found" % options.keychain_access_group)
61
+ return 1
62
+
63
+ currentLineNumber += 1
64
+
65
+ print_error('App must be signed')
66
+ return 1
67
+
68
+ def print_error(error_message):
69
+ """ Prints error message with predefined prefix.
70
+
71
+ Args:
72
+ error_message: Error message.
73
+ """
74
+
75
+ XCODE_ERROR_PREFIX = 'error: ' # log messages with such prefix are highlighted in XCode as errors
76
+
77
+ print('%s%s' % (XCODE_ERROR_PREFIX, error_message))
78
+
79
+ if __name__ == '__main__':
80
+ sys.exit(main())
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/perl
2
+
3
+ # svn-clean - Wipes out unversioned files from SVN working copy.
4
+ # Copyright (C) 2004, 2005, 2006 Simon Perreault
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2
9
+ # of the License, or (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+
20
+ use strict;
21
+ use Cwd;
22
+ use File::Path;
23
+ use Getopt::Long;
24
+ use Pod::Usage;
25
+
26
+ # Try to use SVN module if available.
27
+ my $use_svn_module = eval { require SVN::Client };
28
+
29
+ my $CWD = getcwd;
30
+
31
+ my @exclude = ();
32
+ my $force = 0;
33
+ my $quiet = 0;
34
+ my $print = 0;
35
+ my $help = 0;
36
+ my $man = 0;
37
+ my $nonrecursive = 0;
38
+ my @paths = ($CWD);
39
+ GetOptions(
40
+ "exclude=s" => \@exclude,
41
+ "force" => \$force,
42
+ "non-recursive|N" => \$nonrecursive,
43
+ "quiet" => \$quiet,
44
+ "print" => \$print,
45
+ "help|?" => \$help,
46
+ "man" => \$man
47
+ ) or pod2usage(2);
48
+ pod2usage(1) if $help;
49
+ pod2usage( -exitstatus => 0, -verbose => 2 ) if $man;
50
+ @paths = map { Cwd::abs_path($_) } @ARGV if @ARGV;
51
+
52
+ # Precompile regexes.
53
+ $_ = qr/$_/ foreach @exclude;
54
+
55
+ if ($use_svn_module) {
56
+
57
+ # Create SVN client object. No need for connection to remote server.
58
+ my $ctx = new SVN::Client;
59
+
60
+ # Call handler function with status info for each file.
61
+ $ctx->status( $_, undef, \&clean, !$nonrecursive, 1, 0, 1 )
62
+ for @paths;
63
+ }
64
+ else {
65
+ warn "Warning: Not using SVN Perl modules, this might be slow.\n"
66
+ unless $quiet;
67
+
68
+ # Build svn client command
69
+ my @command = qw(svn status --no-ignore -v);
70
+ if ($nonrecursive) {
71
+ push @command, '-N';
72
+ }
73
+
74
+ # Main file-wiping loop.
75
+ if ( $^O eq 'MSWin32' ) {
76
+
77
+ # Perl on Windows currently doesn't have list pipe opens.
78
+ open SVN, join( ' ', @command, @paths ) . '|'
79
+ or die "Can't call program \"svn\": $!\n";
80
+ }
81
+ else {
82
+ open SVN, "-|", @command, @paths
83
+ or die "Can't call program \"svn\": $!\n";
84
+ }
85
+ LINE: while (<SVN>) {
86
+ if (/^([\?ID])/) {
87
+ my $file = (split)[-1];
88
+ foreach my $ex (@exclude) {
89
+ if ( $file =~ $ex ) {
90
+ print "excluded $file\n" unless $quiet or $print;
91
+ next LINE;
92
+ }
93
+ }
94
+ if ( $1 eq 'D' ) {
95
+ next unless -f $file;
96
+ }
97
+ else {
98
+ next unless -e $file;
99
+ }
100
+ if ($print) {
101
+ print "$file\n";
102
+ }
103
+ else {
104
+ rmtree( $file, !$quiet, !$force );
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ # Main file-wiping function.
111
+ sub clean {
112
+ my ( $path, $status ) = @_;
113
+
114
+ # Use relative path for pretty-printing.
115
+ if ( $path =~ s/^\Q$CWD\E\/?//o ) {
116
+
117
+ # If the substitution succeeded, we should have a relative path. Make
118
+ # sure we don't delete critical stuff.
119
+ return if $path =~ /^\//;
120
+ }
121
+
122
+ # Find files needing to be removed.
123
+ if ( $status->text_status == $SVN::Wc::Status::unversioned
124
+ or $status->text_status == $SVN::Wc::Status::ignored
125
+ or $status->text_status == $SVN::Wc::Status::deleted )
126
+ {
127
+ foreach my $ex (@exclude) {
128
+ if ( $path =~ $ex ) {
129
+ print "excluded $path\n" unless $quiet or $print;
130
+ return;
131
+ }
132
+ }
133
+
134
+ # Make sure the file exists before removing it. Do not remove deleted
135
+ # directories as they are needed to remove the files they contain when
136
+ # committing.
137
+ lstat $path or stat $path;
138
+ if (
139
+ -e _
140
+ and ( not -d _
141
+ or $status->text_status != $SVN::Wc::Status::deleted )
142
+ )
143
+ {
144
+ if ($print) {
145
+ print "$path\n";
146
+ }
147
+ else {
148
+ rmtree( $path, !$quiet, !$force );
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ __END__
155
+
156
+ =head1 NAME
157
+
158
+ svn-clean - Wipes out unversioned files from Subversion working copy
159
+
160
+ =head1 SYNOPSIS
161
+
162
+ svn-clean [options] [directory or file ...]
163
+
164
+ =head1 DESCRIPTION
165
+
166
+ B<svn-clean> will scan the given files and directories recursively and find
167
+ unversioned files and directories (files and directories that are not present in
168
+ the Subversion repository). After the scan is done, these files and directories
169
+ will be deleted.
170
+
171
+ If no file or directory is given, B<svn-clean> defaults to the current directory
172
+ (".").
173
+
174
+ B<svn-clean> uses the SVN Perl modules if they are available. This is much
175
+ faster than parsing the output of the B<svn> command-line client.
176
+
177
+ =head1 OPTIONS
178
+
179
+ =over 8
180
+
181
+ =item B<-e>, B<--exclude>
182
+
183
+ A regular expression for filenames to be exluded. For example, the following
184
+ command will skip files ending in ".zip":
185
+
186
+ =over 8
187
+
188
+ svn-clean --exclude '\.zip$'
189
+
190
+ =back
191
+
192
+ Multiple exclude patterns can be specified. If at least one matches, then the
193
+ file is skipped. For example, the following command will skip files ending in
194
+ ".jpg" or ".png":
195
+
196
+ =over 8
197
+
198
+ svn-clean --exclude '\.jpg$' --exclude '\.png$'
199
+
200
+ =back
201
+
202
+ The following command will skip the entire "build" subdirectory:
203
+
204
+ =over 8
205
+
206
+ svn-clean --exclude '^build(/|$)'
207
+
208
+ =back
209
+
210
+ =item B<-f>, B<--force>
211
+
212
+ Files to which you do not have delete access (if running under VMS) or write
213
+ access (if running under another OS) will not be deleted unless you use this
214
+ option.
215
+
216
+ =item B<-N>, B<--non-recursive>
217
+
218
+ Do not search recursively for unversioned files and directories. Unversioned
219
+ directories will still be deleted along with all their contents.
220
+
221
+ =item B<-q>, B<--quiet>
222
+
223
+ Do not print progress info. In particular, do not print a message each time a
224
+ file is examined, giving the name of the file, and indicating whether "rmdir" or
225
+ "unlink" is used to remove it, or that it's skipped.
226
+
227
+ =item B<-p>, B<--print>
228
+
229
+ Do not delete anything. Instead, print the name of every file and directory that
230
+ would have been deleted.
231
+
232
+ =item B<-?>, B<-h>, B<--help>
233
+
234
+ Prints a brief help message and exits.
235
+
236
+ =item B<--man>
237
+
238
+ Prints the manual page and exits.
239
+
240
+ =back
241
+
242
+ =head1 AUTHOR
243
+
244
+ Simon Perreault <nomis80@nomis80.org>
245
+
246
+ =cut