SVNHelper_Command::getSVN()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace AppUtils;
4
5
abstract class SVNHelper_Command
6
{
7
    public const ERROR_INVALID_COMMAND_RESULT = 22601;
8
    
9
    public const ERROR_UNEXPECTED_OUTPUT = 22602;
10
    
11
    public const ERROR_CONFLICTS_REPORTED = 22603;
12
    
13
    public const ERROR_REPOSITORY_LOCKED = 22604;
14
    
15
    public const ERROR_CONNECTION_FAILED = 22605;
16
    
17
    public const SVN_ERROR_IGNORED = 8000001;
18
    
19
    public const SVN_ERROR_TYPE_ERROR = 'error';
20
    
21
    public const SVN_ERROR_TYPE_WARNING = 'warning';
22
    
23
    /**
24
    * @var SVNHelper
25
    */
26
    protected $helper;
27
    
28
   /**
29
    * @var SVNHelper_Target
30
    */
31
    protected $target;
32
    
33
   /**
34
    * @var SVNHelper_CommandResult|NULL
35
    */
36
    protected ?SVNHelper_CommandResult $result;
37
    
38
    public function __construct(SVNHelper $helper, SVNHelper_Target $target)
39
    {
40
        $this->helper = $helper;
41
        $this->target = $target;
42
    }
43
    
44
   /**
45
    * @return SVNHelper
46
    */
47
    public function getSVN()
48
    {
49
        return $this->helper;
50
    }
51
    
52
    public function getTarget()
53
    {
54
        return $this->target;
55
    }
56
    
57
    public function execute()
58
    {
59
        if(isset($this->result))
60
        {
61
            return $this->result;
62
        }
63
        
64
        // adjust environment locale for the SVN unicode features to work properly.
65
        $locale = 'en_US.UTF-8';
66
        setlocale(LC_ALL, $locale);
67
        putenv('LC_ALL='.$locale);
68
        
69
        $this->result = $this->_execute();
70
        if(!$this->result instanceof SVNHelper_CommandResult) {
71
            throw new SVNHelper_Exception(
72
                'Not a valid SVN command result',
73
                sprintf(
74
                    'The command [%s] did not return an [SVNHelper_CommandResult] instance.',
75
                    get_class($this)
76
                ),
77
                self::ERROR_INVALID_COMMAND_RESULT
78
            );
79
        }
80
        
81
        return $this->result;
82
    }
83
    
84
    abstract protected function _execute();
85
86
    protected function buildCommand($mode, $path=null, $params=array())
87
    {
88
        $params[] = 'non-interactive';
89
        $params[] = 'username '.$this->helper->getAuthUser();
90
        $params[] = 'password '.$this->helper->getAuthPassword();
91
        
92
        $cmd = 'svn '.$mode.' '.$path.' ';
93
        
94
        foreach($params as $param) {
95
            $cmd .= '--'.$param.' ';
96
        }
97
        
98
        $cmd .= '2>&1';
99
        
100
        return $cmd;
101
    }
102
103
   /**
104
    * Executes the specified command, and returns a result
105
    * instance to read the results.
106
    * 
107
    * @param string $mode The command mode, e.g. commit / update...
108
    * @param string $path The path to apply the command to
109
    * @param array $params
110
    * @return SVNHelper_CommandResult
111
    */
112
    protected function execCommand($mode, $path=null, $params=array())
113
    {
114
        $relative = $this->helper->relativizePath($path);
115
        
116
        $this->log(sprintf(
117
            '[%s] | Executing ['.$mode.'].',
118
            $relative
119
        ));
120
        
121
        $cmd = $this->buildCommand($mode, $path, $params);
122
        
123
        $output = array();
124
        exec($cmd, $output);
125
        
126
        $lines = array();
127
        foreach($output as $line) {
128
            $lines[] = mb_strtolower(trim(utf8_encode($line)));
129
        }
130
        
131
        $errorMessages = array();
132
        
133
        // command was skipped for some reason. We have to check
134
        // for it this way, because this error is not shown like
135
        // other regular errors.
136
        //
137
        // Can happen for example when the path is not under version
138
        // control.
139
        if(isset($lines[0]) && substr($lines[0], 0, 7) == 'skipped')
140
        {
141
            $tokens = explode('--', $lines[0]);
142
            $message = trim(array_pop($tokens));
143
            
144
            $error = new SVNHelper_CommandError(
145
                $this, 
146
                self::SVN_ERROR_TYPE_ERROR, 
147
                $message, 
148
                self::SVN_ERROR_IGNORED
149
            );
150
            
151
            $errorMessages[] = $error;
152
        }
153
        // search for error codes. SVN adds lines in
154
        // the following format:
155
        //
156
        // svn: e123456: error message
157
        // svn: w123456: warning message
158
        else
159
        {
160
            foreach($lines as $line) 
161
            {
162
                if(strstr($line, 'svn:')) 
163
                {
164
                    $result = array();
165
                    preg_match_all('/svn:[ ]*(e|warning:[ ]*w)([0-9]+):(.*)/', $line, $result, PREG_PATTERN_ORDER);
166
                    
167
                    if(isset($result[1]) && isset($result[1][0])) 
168
                    {
169
                        $message = trim($result[3][0]);
170
                        $code = trim($result[2][0]);
171
                        $type = self::SVN_ERROR_TYPE_ERROR;
172
                        
173
                        if($result[1][0] != 'e') {
174
                            $type = self::SVN_ERROR_TYPE_WARNING;
175
                        }
176
177
                        $error = new SVNHelper_CommandError($this, $type, $message, $code);
178
                        $errorMessages[] = $error;
179
                    }
180
                }
181
            }
182
        }
183
        
184
        $result = new SVNHelper_CommandResult($this, $cmd, $lines, $errorMessages);
185
        
186
        if($result->isError()) {
187
            $this->log(sprintf('[%s] | Command returned errors.', $relative));
188
        } 
189
        
190
        return $result;
191
    }
192
    
193
    public function getResult()
194
    {
195
        return $this->result;
196
    }
197
    
198
   /**
199
    * Retrieves the type of command, e.g. "Commit"
200
    * @return string
201
    */
202
    public function getType()
203
    {
204
        return str_replace('SVNHelper_Command_', '', get_class($this));   
205
    }
206
    
207
    protected function throwExceptionUnexpected(SVNHelper_CommandResult $result)
208
    {
209
        if($result->isConnectionFailed()) {
210
            $this->throwException(
211
                t('Could not connect to the remote SVN repository'), 
212
                '', 
213
                self::ERROR_CONNECTION_FAILED, 
214
                $result
215
            );
216
        }
217
        
218
        if($result->hasConflicts()) {
219
            $this->throwException(
220
                t('SVN command reported conflicts'), 
221
                '', 
222
                self::ERROR_CONFLICTS_REPORTED, 
223
                $result
224
            );
225
        }
226
        
227
        if($result->hasLocks()) {
228
            $this->throwException(
229
                t('The target SVN folder or file is locked.'), 
230
                '', 
231
                self::ERROR_REPOSITORY_LOCKED, 
232
                $result
233
            );
234
        }
235
        
236
        $this->throwException(
237
            t('SVN command returned unexpected errors'),
238
            '',
239
            self::ERROR_UNEXPECTED_OUTPUT,
240
            $result
241
        );
242
    }
243
    
244
    protected function throwException($message, $details, $code, SVNHelper_CommandResult $result, $previous=null)
245
    {
246
        $body = 
247
        '<p>'.
248
            'Command: '.$this->getType().
249
        '</p>'.
250
        '<p>'.
251
            'Details:'.
252
        '</p>'.
253
        '<p>'.$details.'</p>'.
254
        '<p>'.
255
            'Result error messages:'.
256
        '</p>'.
257
        '<ul>';
258
            $errors = $result->getErrorMessages();
259
            foreach($errors as $error) {
260
                $body .= 
261
                '<li>'.
262
                    $error.
263
                '</li>';
264
            }
265
            $body .=
266
        '</ul>'.
267
        '<p>'.
268
            'Raw SVN command line output:'.
269
        '</p>'.
270
        '<pre>'.implode(PHP_EOL, $result->getOutput()).'</pre>'.
271
        '<p>'.
272
            'Command line:'.
273
        '</p>'.
274
        '<code>'.$result->getCommandLine().'</code>';
275
         
276
            
277
        throw new SVNHelper_CommandException($message, $body, $code, $result, $previous);
278
    }
279
    
280
    protected function log($message)
281
    {
282
        SVNHelper::log($message);
283
    }
284
}