Issues (47)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Git/WorkingCopy.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Hubph\Git;
4
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerAwareTrait;
7
use Symfony\Component\Filesystem\Filesystem;
8
use Hubph\Util\ExecWithRedactionTrait;
9
10
class WorkingCopy implements LoggerAwareInterface
11
{
12
    use ExecWithRedactionTrait;
13
    use LoggerAwareTrait;
14
15
    protected $remote;
16
    protected $fork;
17
    protected $dir;
18
    protected $api;
19
20
    const FORCE_MERGE_COMMIT = 0x01;
21
22
    /**
23
     * WorkingCopy constructor
24
     *
25
     * @param $url Remote origin for the GitHub repository
26
     * @param $dir Checkout location for the project
27
     */
28
    protected function __construct($url, $dir, $branch = false, $api = null)
0 ignored issues
show
The parameter $branch is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
29
    {
30
        $this->remote = new Remote($url);
31
        $this->remote->addAuthentication($api);
32
        $this->dir = $dir;
33
        $this->api = $api;
34
35
        $this->confirmCachedRepoHasCorrectRemote();
36
    }
37
38
    public static function fromDir($dir, $remoteName = 'origin', $api = null)
39
    {
40
        $remote = Remote::fromDir($dir, $remoteName);
41
42
        return new self($remote->url(), $dir, false, $api);
43
    }
44
45
    /**
46
     * addFork will set a secondary remote on this repository.
47
     * The purpose of having a fork remote is if the primary repository
48
     * is read-only. If a fork is set, then any branches pushed
49
     * will go to the fork; any pull request created will still be
50
     * set on the primary repository, but will refer to the branch on
51
     * the fork.
52
     */
53
    public function addFork($fork_url)
54
    {
55
        if (empty($fork_url)) {
56
            $this->fork = null;
57
            return $this;
58
        }
59
        $this->fork = new Remote($fork_url);
60
        $this->fork->addAuthentication($this->api);
61
62
        return $this;
63
    }
64
65
    /**
66
     * createFork creates a new secondary repository copied from
67
     * the current repository, and sets it up as a fork per 'addFork'.
68
     */
69
    public function createFork($forked_project_name, $forked_org = '', $branch = '')
70
    {
71
        $result = $this->api->repoCreate(empty($forked_org) ? $this->org() : $forked_org, $forked_project_name);
72
73
        // 'git_url' => 'git://github.com/org/project.git',
74
        // 'ssh_url' => '[email protected]:org/project.git',
75
76
        $fork_url = $result['ssh_url'];
77
        $result = $this->addFork($fork_url);
78
79
        $this->push('fork', $branch);
80
81
        return $result;
82
    }
83
84
    public function deleteFork()
85
    {
86
        if (!$this->fork) {
87
            return;
88
        }
89
90
        $this->api->repoDelete($this->fork->org(), $this->fork->project());
91
    }
92
93
    /**
94
     * forkUrl returns the URL of the forked repository that should
95
     * be used for creating any pull requests.
96
     */
97
    public function forkUrl()
98
    {
99
        if (!$this->fork) {
100
            return null;
101
        }
102
        return $this->fork->url();
103
    }
104
105
    public function forkProjectWithOrg()
106
    {
107
        if (!$this->fork) {
108
            return null;
109
        }
110
        return $this->fork->projectWithOrg();
111
    }
112
113
    public function remoteFork()
114
    {
115
        if (!$this->fork) {
116
            return null;
117
        }
118
        return $this->fork;
119
    }
120
121
    /**
122
     * Clone the specified repository to the given URL at the indicated
123
     * directory. If the desired repository already exists there, then
124
     * we will re-use it rather than re-clone the repository.
125
     *
126
     * @param string $url
127
     * @param string $dir
128
     * @param HubphAPI|null $api
129
     * @return WorkingCopy
130
     */
131
    public static function clone($url, $dir, $api = null)
132
    {
133
        return static::cloneBranch($url, $dir, false, $api);
0 ignored issues
show
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
134
    }
135
136
    /**
137
     * Clone the specified repository to the given URL at the indicated
138
     * directory. Only clone a single commit. Since we're only interested
139
     * in one commit, we'll just remove the cache if it is present.
140
     *
141
     * @param string $url
142
     * @param string $dir
143
     * @param string $branch
144
     * @param HubphAPI|null $api
145
     * @return WorkingCopy
146
     */
147
    public static function shallowClone($url, $dir, $branch, $depth = 1, $api = null)
148
    {
149
        $workingCopy = new self($url, $dir, $branch, $api);
0 ignored issues
show
$branch is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
150
        $workingCopy->freshClone($branch, $depth);
0 ignored issues
show
$branch is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$depth is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
151
        return $workingCopy;
152
    }
153
154
    public static function unclonedReference($url, $dir, $branch, $api = null)
155
    {
156
        return new self($url, $dir, $branch, $api);
157
    }
158
159
    /**
160
     * Clone the specified branch of the specified repository to the given URL.
161
     *
162
     * @param string $url
163
     * @param string $dir
164
     * @param string $branch
165
     * @param HubphAPI|null $api
166
     * @return WorkingCopy
167
     */
168
    public static function cloneBranch($url, $dir, $branch, $api, $depth = false)
169
    {
170
        $workingCopy = new self($url, $dir, $branch, $api);
0 ignored issues
show
$branch is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
171
        $workingCopy->cloneIfNecessary($branch, $depth);
0 ignored issues
show
$branch is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
172
        return $workingCopy;
173
    }
174
175
    /**
176
     * take tranforms this local working copy such that it RETAINS all of its
177
     * local files (no change to any unstaged modifications or files) and
178
     * TAKES OVER the repository from the provided working copy.
179
     *
180
     * The local repository that was formerly in place here is disposed.
181
     * Any branches or commits not already pushed to the remote repository
182
     * are lost. Only the working files remain. The remotes for this working
183
     * copy become the remotes from the provided repository.
184
     *
185
     * The other working copy is disposed: its files are all removed
186
     * from the filesystem.
187
     */
188
    public function take(WorkingCopy $rhs)
189
    {
190
        $fs = new Filesystem();
191
192
        $ourLocalGitRepo = $this->dir() . '/.git';
193
        $rhsLocalGitRepo = $rhs->dir() . '/.git';
194
195
        $fs->remove($ourLocalGitRepo);
196
        $fs->rename($rhsLocalGitRepo, $ourLocalGitRepo);
197
198
        $this->remote = $rhs->remote();
199
        $this->addFork($rhs->forkUrl());
200
    }
201
202
    /**
203
     * remove will delete all of the local working files managed by this
204
     * object, including the '.git' directory. This method should be called
205
     * if the local working copy is corrupted or otherwise becomes unusable.
206
     */
207
    public function remove()
208
    {
209
        $fs = new Filesystem();
210
        $fs->remove($this->dir());
211
    }
212
213
    public function remote($remote_name = '')
214
    {
215
        if (empty($remote_name) || ($remote_name == 'origin')) {
216
            return $this->remote;
217
        }
218
        return Remote::fromDir($this->dir, $remote_name);
219
    }
220
221
    public function url($remote_name = '')
222
    {
223
        return $this->remote($remote_name)->url();
224
    }
225
226
    public function dir()
227
    {
228
        return $this->dir;
229
    }
230
231
    public function valid()
232
    {
233
        return $this->remote->valid();
234
    }
235
236
    public function org($remote_name = '')
237
    {
238
        return $this->remote($remote_name)->org();
239
    }
240
241
    public function project($remote_name = '')
242
    {
243
        return $this->remote($remote_name)->project();
244
    }
245
246
    public function projectWithOrg($remote_name = '')
247
    {
248
        return $this->remote($remote_name)->projectWithOrg();
249
    }
250
251
    /**
252
     * List modified files.
253
     */
254
    public function status()
255
    {
256
        return $this->git('status --porcelain');
257
    }
258
259
    /**
260
     * Fetch from the specified remote.
261
     */
262
    public function fetch($remote, $branch)
263
    {
264
        $this->git('fetch {remote} {branch}', ['remote' => $remote, 'branch' => $branch]);
265
        return $this;
266
    }
267
268
    /**
269
     * Fetch from the specified remote.
270
     */
271
    public function fetchTags($remote = 'origin')
272
    {
273
        $this->fetch($remote, '--tags');
274
        return $this;
275
    }
276
277
    /**
278
     * Pull from the specified remote.
279
     */
280
    public function pull($remote, $branch)
281
    {
282
        $this->git('pull {remote} {branch}', ['remote' => $remote, 'branch' => $branch]);
283
        return $this;
284
    }
285
286
    /**
287
     * Push the specified branch to the desired remote.
288
     */
289
    public function push($remote = '', $branch = '', $force = false)
290
    {
291
        if (empty($remote)) {
292
            $remote = isset($this->fork) ? 'fork' : 'origin';
293
        }
294
        if (empty($branch)) {
295
            $branch = $this->branch();
296
        }
297
        $flag = $force ? '--force ' : '';
298
        $this->git('push {flag}{remote} {branch}', ['remote' => $remote, 'branch' => $branch, 'flag' => $flag]);
299
        return $this;
300
    }
301
302
    /**
303
     * Force-push the branch
304
     */
305
    public function forcePush($remote = '', $branch = '')
306
    {
307
        return $this->push($remote, $branch, true);
308
    }
309
310
    /**
311
     * Merge the specified branch into the current branch.
312
     */
313
    public function merge($branch, $modes = 0)
314
    {
315
        $flags = '';
316
        if ($modes & static::FORCE_MERGE_COMMIT) {
317
            $flags .= ' --no-ff';
318
        }
319
320
        $this->git('merge{flags} {branch}', ['branch' => $branch, 'flags' => $flags]);
321
        return $this;
322
    }
323
324
    public function cherryPick($sha)
325
    {
326
        $this->git('cherry-pick {sha}', ['sha' => $sha]);
327
        return $this;
328
    }
329
330
    /**
331
     * Reset to the specified reference.
332
     */
333
    public function reset($ref = '', $hard = false)
334
    {
335
        $flag = $hard ? '--hard ' : '';
336
        $this->git('reset {flag}{ref}', ['ref' => $ref, 'flag' => $flag]);
337
    }
338
339
    /**
340
     * switchBranch is a synonym for 'checkout'
341
     */
342
    public function switchBranch($branch)
343
    {
344
        $this->git('checkout {branch}', ['branch' => $branch]);
345
        return $this;
346
    }
347
348
    /**
349
     * Switch to the specified branch. Use 'createBranch' to create a new branch.
350
     */
351
    public function checkout($branch)
352
    {
353
        $this->git('checkout {branch}', ['branch' => $branch]);
354
        return $this;
355
    }
356
357
    /**
358
     * Create a new branch
359
     */
360
    public function createBranch($branch, $base = '', $force = false)
361
    {
362
        $flag = $force ? '-B' : '-b';
363
        $this->git('checkout {flag} {branch} {base}', ['branch' => $branch, 'base' => $base, 'flag' => $flag]);
364
        return $this;
365
    }
366
367
    /**
368
     * Stage the items at the specified path.
369
     */
370
    public function add($itemsToAdd)
371
    {
372
        $this->git('add ' . $itemsToAdd);
373
        return $this;
374
    }
375
376
    /**
377
     * Stage everything
378
     */
379
    public function addAll()
380
    {
381
        $this->git('add -A --force .');
382
        return $this;
383
    }
384
385
    /**
386
     * Commit the staged changes.
387
     *
388
     * @param string $message
389
     * @param bool $amend
390
     */
391
    public function commit($message, $amend = false)
392
    {
393
        $flag = $amend ? '--amend ' : '';
394
        $this->git("commit {flag}-m '{message}'", ['message' => $message, 'flag' => $flag]);
395
        return $this;
396
    }
397
398
    /**
399
     * Commit the staged changes by a specified user at specified date.
400
     *
401
     * @param string $message
402
     * @param string $author
403
     * @param string $commit_date
404
     * @param bool $amend
405
     */
406
    public function commitBy($message, $author, $commit_date, $amend = false)
407
    {
408
        $flag = $amend ? '--amend ' : '';
409
        $this->git("commit {flag}-m '{message}' --author='{author}' --date='{date}'", ['message' => $message, 'author' => $author, 'date' => $commit_date, 'flag' => $flag]);
410
        return $this;
411
    }
412
413
    /**
414
     * Ammend the top commit without altering the message.
415
     */
416
    public function amend()
417
    {
418
        return $this->commit($this->message(), true);
419
    }
420
421
    /**
422
     * Add a tag
423
     */
424
    public function tag($tag, $ref = '')
425
    {
426
        $this->git("tag $tag $ref");
427
        return $this;
428
    }
429
430
    /**
431
     * Return the commit message for the sprecified ref
432
     */
433
    public function message($ref = 'HEAD')
434
    {
435
        return trim(implode("\n", $this->git('log --format=%B -n 1 {ref}', ['ref' => $ref])));
436
    }
437
438
    /**
439
     * Return the commit date for the sprecified ref
440
     */
441
    public function commitDate($ref = 'HEAD')
442
    {
443
        return trim(implode("\n", $this->git('log -1 --date=iso --pretty=format:"%cd" {ref}', ['ref' => $ref])));
444
    }
445
446
    public function branch($ref = 'HEAD')
447
    {
448
        return trim(implode("\n", $this->git('rev-parse --abbrev-ref {ref}', ['ref' => $ref])));
449
    }
450
451
    public function revParse($ref)
452
    {
453
        return trim(implode("\n", $this->git('rev-parse {ref}', ['ref' => $ref])));
454
    }
455
456
    /**
457
     * Show a diff of the current modified and uncommitted files.
458
     */
459
    public function diff()
460
    {
461
        return trim(implode("\n", $this->git('diff')));
462
    }
463
464
    /**
465
     * Create a pull request.
466
     *
467
     * @param string $message
468
     * @return $this
469
     */
470
    public function pr($message, $body = '', $base = 'master', $head = '', $forked_org = '')
0 ignored issues
show
The parameter $forked_org is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
471
    {
472
        if (empty($head)) {
473
            $head = $this->branch();
474
        }
475
        if (isset($this->fork)) {
476
            $forked_org = $this->fork->org();
477
            $head = "$forked_org:$head";
478
        }
479
480
        $this->logger->notice('Create pull request for {org_project} using {head} from {base}', ['org_project' => $this->projectWithOrg(), 'head' => $head, 'base' => $base]);
481
482
        $result = $this->api->prCreate($this->org(), $this->project(), $message, $body, $base, $head);
0 ignored issues
show
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
483
484
        return $this;
485
    }
486
487
    /**
488
     * Show a diff of the specified reference from the commit before it.
489
     */
490
    public function show($ref = "HEAD")
491
    {
492
        return implode("\n", $this->git("show $ref"));
493
    }
494
495
    /**
496
     * Add a remote (or change the URL to an existing remote)
497
     */
498
    public function addRemote($url, $remote)
499
    {
500
        return static::setRemoteUrl($url, $this->dir, $remote);
501
    }
502
503
    /**
504
     * If the directory exists, check its remote. Fail if there is
505
     * some project there that is not the requested project.
506
     */
507
    protected function confirmCachedRepoHasCorrectRemote($emptyOk = false)
508
    {
509
        if (!file_exists($this->dir)) {
510
            return;
511
        }
512
        // Check to see if the remote origin is already set to our exact url
513
        $currentURL = exec("git -C {$this->dir} config --get remote.origin.url", $output, $result);
514
515
        if ($currentURL == $this->url()) {
516
            return;
517
        }
518
        // If the API exists, try to repair the URL if the existing URL is close
519
        // (e.g. someone switched authentication tokens)
520
        if ($this->api) {
521
            if (($emptyOk && empty($currentURL)) || ($this->api->addTokenAuthentication($currentURL) == $this->url())) {
522
                static::setRemoteUrl($this->url(), $this->dir);
523
                return;
524
            }
525
        }
526
527
        // TODO: This error message is a potential credentials leak
528
        throw new \Exception("Directory `{$this->dir}` exists and is a clone of `$currentURL` rather than `{$this->url()}`");
529
    }
530
531
    /**
532
     * Set the remote origin to the provided url
533
     * @param string $url
534
     * @param string $dir
535
     * @param string $remote
536
     */
537
    protected static function setRemoteUrl($url, $dir, $remote = 'origin')
538
    {
539
        if (is_dir($dir)) {
540
            $currentURL = exec("git -C {$dir} config --get remote.{$remote}.url");
541
            $gitCommand = empty($currentURL) ? 'add' : 'set-url';
542
            exec("git -C {$dir} remote {$gitCommand} {$remote} {$url}");
543
        }
544
        $remote = new Remote($url);
545
546
        return $remote;
547
    }
548
549
    /**
550
     * If the directory does not exist, then clone it.
551
     */
552
    public function cloneIfNecessary($branch = false, $depth = false)
553
    {
554
        // If the directory exists, we have already validated that it points
555
        // at the correct repository.
556
        if (!is_dir($this->dir)) {
557
            $this->freshClone($branch, $depth);
558
        }
559
        // Make sure that we are on 'master' (or the specified branch) and up-to-date.
560
        $branchTerm = $branch ?: 'master';
561
        exec("git -C '{$this->dir}' reset --hard 2>/dev/null", $output, $result);
562
        exec("git -C '{$this->dir}' checkout $branchTerm 2>/dev/null", $output, $result);
563
        exec("git -C '{$this->dir}' pull origin $branchTerm 2>/dev/null", $output, $result);
564
    }
565
566
    protected function freshClone($branch = false, $depth = false)
567
    {
568
        // Remove $this->dir if it exists, then make sure its parents exist.
569
        $fs = new Filesystem();
570
        if (is_dir($this->dir)) {
571
            $fs->remove($this->dir);
572
        }
573
        $fs->mkdir(dirname($this->dir));
574
575
        $branchTerm = $branch ? "--branch=$branch " : '';
576
        $depthTerm = $depth ? "--depth=$depth " : '';
577
        exec("git clone '{$this->url()}' $branchTerm$depthTerm'{$this->dir}' 2>/dev/null", $output, $result);
578
579
        // Fail if we could not clone.
580
        if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
581
            $project = $this->projectWithOrg();
582
            throw new \Exception("Could not clone $project: git failed with exit code $result");
583
        }
584
    }
585
586
    /**
587
     * Run a git function on the local working copy. Fail on error.
588
     *
589
     * @return string stdout
590
     */
591
    public function git($cmd, $replacements = [], $redacted = [])
592
    {
593
        return $this->execWithRedaction('git {dir}' . $cmd, ['dir' => "-C {$this->dir} "] + $replacements, ['dir' => ''] + $redacted);
594
    }
595
}
596