├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── screen.jpg
├── screen2.jpg
└── src
├── Config
└── gitup.php
├── Facades
└── GitUp.php
├── Git.php
├── GitUpServiceProvider.php
├── Http
├── Controllers
│ └── GitUpController.php
└── routes.php
├── Migrations
└── 2016_09_12_99999_create_commits_table.php
├── Uploader
├── Connectors
│ ├── FTP.php
│ └── SFTP.php
├── Contracts
│ └── ConnectorInterface.php
├── Traits
│ └── Options.php
└── Uploader.php
└── Views
├── files.blade.php
├── index.blade.php
├── layout
└── gitup.blade.php
├── preview.blade.php
└── statistics.blade.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | push.sh
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Sarfraz Ahmed
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gitup
2 |
3 | [](http://laravel.com)
4 | [](http://laravel.com)
5 | [](http://laravel.com)
6 | [](http://laravel.com)
7 | [](http://laravel.com)
8 | [](https://packagist.org/packages/sarfraznawaz2005/gitup)
9 |
10 | Laravel package to upload git commits to server(s) via (s)ftp.
11 |
12 | ## DISCLAIMER ##
13 |
14 | This package is not fully tested, **use it at your own risk!**
15 |
16 | ## Screenshot ##
17 |
18 | 
19 |
20 | 
21 |
22 |
23 | ## Why ##
24 |
25 | We have multiple servers eg live, staging, testing, etc and client wanted us to upload task/story # X to staging only or story Y to live only that's when it was hard to track down files worked upon earlier and then upload them selectively; a time consuming process and nuisance so we created this package so that we can easily upload with one click selected stories to asked servers.
26 |
27 | ## Requirements ##
28 |
29 | - PHP >= 5.6
30 | - `git` added to PATH env
31 | - `FTP` and `Zip` PHP extensions (both ship with PHP and usually turned on)
32 | - `league/flysystem` FTP wrapper used by gitUp. (comes with laravel by default)
33 | - `league/flysystem-sftp` Library used by gitUp to upload files via SFTP.
34 |
35 | ## Installation ##
36 |
37 | Install via composer
38 | ```
39 | composer require sarfraznawaz2005/gitup
40 | ```
41 |
42 | For Laravel < 5.5:
43 |
44 | Add Service Provider to `config/app.php` in `providers` section
45 | ```php
46 | Sarfraznawaz2005\GitUp\GitUpServiceProvider::class,
47 | ```
48 |
49 | ---
50 |
51 | Run `php artisan vendor:publish` to publish package's config and migration file. You should now have `config/gitup.php` file published. It will also publish migration file in `database/migrations` folder.
52 |
53 | Run `php artisan migrate` to create `commits` table in your database.
54 |
55 | Check and update `config/gitup.php` file to setup config options including S(FTP) server information where you would like to upload.
56 |
57 | By default, gitup UI is available at `/gitup` route.
58 |
59 | ## How it Works ##
60 |
61 | For selected commits, we extract files out of them and create zip archive along with an script to extract this zip archive. The zip archive and extract script are then uploaded to selected server where extract script extracts the uploaded zip archive. Once the upload process is done, both zip archive and extract script are deleted from the server.
62 |
63 | Uploading zip archive along with extract script has huge speed benefits as all committed files get uploaded in one shot as opposed to uploading each committed file individually.
64 |
65 | ## Similar Project ##
66 | - [floyer](https://github.com/sarfraznawaz2005/floyer)
67 |
68 | ## License ##
69 |
70 | This code is published under the [MIT License](http://opensource.org/licenses/MIT).
71 | This means you can do almost anything with it, as long as the copyright notice and the accompanying license file is left intact.
72 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sarfraznawaz2005/gitup",
3 | "keywords": [
4 | "laravel",
5 | "deploy",
6 | "ftp",
7 | "upload"
8 | ],
9 | "description": "Laravel package to upload git commits to server(s) via (s)ftp.",
10 | "type": "library",
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Sarfraz Ahmed",
15 | "email": "sarfraznawaz2005@gmail.com"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=5.5.9",
20 | "illuminate/support": "~5"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Sarfraznawaz2005\\GitUp\\": "src/"
25 | }
26 | },
27 | "minimum-stability": "stable",
28 | "extra": {
29 | "laravel": {
30 | "providers": [
31 | "Sarfraznawaz2005\\GitUp\\GitUpServiceProvider"
32 | ]
33 | }
34 | },
35 | "suggest": {
36 | "league/flysystem": "FTP wrapper used by gitUp.",
37 | "league/flysystem-sftp": "Library used by gitUp to upload files via SFTP."
38 | }
39 | }
--------------------------------------------------------------------------------
/screen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sarfraznawaz2005/gitup/69d23618874a2a3312cb6e273db7a756328a5e07/screen.jpg
--------------------------------------------------------------------------------
/screen2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sarfraznawaz2005/gitup/69d23618874a2a3312cb6e273db7a756328a5e07/screen2.jpg
--------------------------------------------------------------------------------
/src/Config/gitup.php:
--------------------------------------------------------------------------------
1 | 'gitup',
7 |
8 | #-------------------------------------------------------------------------
9 |
10 | # If "true", the gitUp page can be viewed by any user who provides
11 | # correct login information (eg all app users).
12 | 'http_authentication' => false,
13 |
14 | #-------------------------------------------------------------------------
15 |
16 | # ignored files/patterns. These files will not be uploaded
17 | 'ignored' => [
18 | '*.scss',
19 | '*.ini',
20 | '.git/*',
21 | '.idea/*',
22 | '.env',
23 | ],
24 |
25 | #-------------------------------------------------------------------------
26 |
27 | # setup one or more servers here with (s)ftp information where you want to upload files
28 | 'servers' => [
29 | 'default' => [
30 | # Connectoin type: FTP or SFTP. Value is case-sensitive
31 | 'connector' => 'FTP',
32 | # Should also include protocol
33 | 'domain' => 'http://mysite.com',
34 | 'host' => 'ftp.toyoursite.com',
35 | 'username' => 'username',
36 | 'password' => 'password',
37 | 'root' => '/',
38 | # Publically accessible folder on server for your app
39 | 'public_path' => '/public',
40 | 'port' => 21,
41 | 'passive' => true,
42 | 'ssl' => false,
43 | # private key file path when connecting via SFTP connector
44 | 'key_file' => 'path/to/privatekey',
45 | ]
46 | ],
47 | ];
48 |
--------------------------------------------------------------------------------
/src/Facades/GitUp.php:
--------------------------------------------------------------------------------
1 | &1');
26 |
27 | return $result ?: '';
28 | }
29 |
30 | public function commitsToday()
31 | {
32 | $yesterday = Carbon::parse(date('Y-m-d'))->subDay(1)->toDateTimeString();
33 |
34 | $commits = $this->execute("git log --after=\"$yesterday\" --pretty=format:\"%cn|%h|%cd|%B\"");
35 |
36 | return $this->getCommitDetails($commits);
37 | }
38 |
39 | public function commitsAll()
40 | {
41 | $commits = $this->execute("git log --pretty=format:\"%cn|%h|%cd|%B\"");
42 |
43 | return $this->getCommitDetails($commits);
44 | }
45 |
46 | public function commitsData(array $commitIds)
47 | {
48 | $commits = '';
49 | foreach ($commitIds as $key => $commit) {
50 | $commits .= $this->execute("git log -1 $commit --pretty=format:\"%cn|%h|%cd|%B\"");
51 | }
52 |
53 | return $this->getCommitDetails($commits);
54 | }
55 |
56 | public function getCommitDetails($commits)
57 | {
58 | $data = [];
59 |
60 | if ($commits) {
61 | $lines = explode("\n", $commits);
62 | $lines = array_filter($lines);
63 |
64 | foreach ($lines as $line) {
65 | if (!trim($line)) {
66 | continue;
67 | }
68 |
69 | $pieces = explode('|', trim($line));
70 | $pieces = array_map('trim', $pieces);
71 |
72 | if (! isset($pieces[2])) {
73 | continue;
74 | }
75 |
76 | $pieces[2] = Carbon::parse(date('Y/m/d H:i:s', strtotime($pieces[2])))->format('m-d-Y H:i:s');
77 |
78 | $data[] = [
79 | 'user' => $pieces[0],
80 | 'commit_id' => $pieces[1],
81 | 'date' => $pieces[2],
82 | 'message' => $pieces[3],
83 | ];
84 | }
85 | }
86 |
87 | return $data;
88 | }
89 |
90 | public function getFiles($commitId = '')
91 | {
92 | $data = [];
93 |
94 | if ($commitId) {
95 | $files = $this->execute("git diff-tree --no-commit-id --name-status -r $commitId");
96 | } else {
97 | // for un-committed files
98 | $files = $this->execute('git status --porcelain');
99 | }
100 |
101 | $files = explode("\n", $files);
102 |
103 | foreach ($files as $file) {
104 | if (!trim($file)) {
105 | continue;
106 | }
107 |
108 | $file = str_replace(["\t", ' '], '|', trim($file));
109 | $pieces = explode('|', $file);
110 | $pieces = array_map('trim', $pieces);
111 |
112 | $data[] = [
113 | 'status' => $pieces[0],
114 | 'file' => $pieces[1]
115 | ];
116 | }
117 |
118 | return $data;
119 | }
120 |
121 | public function getDiffLog($commitId)
122 | {
123 | $data = '';
124 |
125 | $lines = $this->execute("git whatchanged -m -n 1 -p $commitId");
126 | $lines = explode("\n", $lines);
127 |
128 | foreach ($lines as $line) {
129 |
130 | $line = str_replace("\t", str_repeat(' ', 4), $line);
131 | $line = str_replace(' ', ' ', $line);
132 |
133 | if (trim($line) && $line[0] === '+') {
134 | $data .= "$line
";
135 | } elseif (trim($line) && $line[0] === '-') {
136 | $data .= "$line
";
137 | } else {
138 | $data .= $line . '
';
139 | }
140 | }
141 |
142 | return $data;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/GitUpServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->routesAreCached()) {
18 | require __DIR__ . '/Http/routes.php';
19 | }
20 |
21 | // views
22 | $this->loadViewsFrom(__DIR__ . '/Views', 'gitup');
23 |
24 | // publish our files over to main laravel app
25 | $this->publishes([
26 | __DIR__ . '/Config/gitup.php' => config_path('gitup.php'),
27 | __DIR__ . '/Migrations' => database_path('migrations')
28 | ]);
29 | }
30 |
31 | /**
32 | * Register services.
33 | *
34 | * @return void
35 | */
36 | public function register()
37 | {
38 | $this->app->bind('GitUp', function () {
39 | return new Git();
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Http/Controllers/GitUpController.php:
--------------------------------------------------------------------------------
1 | options = config('gitup');
20 |
21 | if (config('gitup.http_authentication')) {
22 | $this->middleware('auth.basic');
23 | }
24 | }
25 |
26 | public function index()
27 | {
28 | $isDirty = false;
29 | $commits = GitUp::commitsAll();
30 |
31 | $files = GitUp::getFiles();
32 |
33 | if ($files) {
34 | $isDirty = true;
35 | }
36 |
37 | $uploadedCommits = collect(DB::table('commits')->get());
38 |
39 | if ($uploadedCommits) {
40 |
41 | if (gettype($uploadedCommits) === 'string') {
42 | $uploadedCommits = [$uploadedCommits];
43 | } else {
44 | $uploadedCommits = $uploadedCommits->toArray();
45 | }
46 |
47 | } else {
48 | $uploadedCommits = [];
49 | }
50 |
51 | $uploadedCommits = collect($uploadedCommits);
52 |
53 | return view('gitup::index', compact('commits', 'isDirty', 'uploadedCommits'));
54 | }
55 |
56 | public function getFiles($commitId = '')
57 | {
58 | $diffLog = '';
59 |
60 | if ($commitId) {
61 | $files = GitUp::getFiles($commitId);
62 | $diffLog = GitUp::getDiffLog($commitId);
63 | } else {
64 | $files = GitUp::getFiles();
65 | }
66 |
67 | return view('gitup::files', compact('files', 'diffLog'));
68 | }
69 |
70 | public function previewUploadFiles()
71 | {
72 | $files = [];
73 | $uploadFiles = [];
74 | $deleteFiles = [];
75 |
76 | $servers = config('gitup.servers');
77 | $commits = request()->commits;
78 |
79 | foreach ($commits as $key => $commit) {
80 | $files[] = GitUp::getFiles($commit);
81 | }
82 |
83 | foreach ($files as $fileArray) {
84 | foreach ($fileArray as $file) {
85 | $type = current($file);
86 | $path = next($file);
87 |
88 | if (!trim($path) || $path == '.' || $path == '..') {
89 | continue;
90 | }
91 |
92 | if ($type && $path) {
93 | if ($type === 'A' || $type === 'C' || $type === 'M' || $type === 'T') {
94 | $uploadFiles[] = $path;
95 | } elseif ($type === 'D') {
96 | $deleteFiles[] = $path;
97 | }
98 | }
99 | }
100 | }
101 |
102 | // do not upload excluded files
103 | $this->uploader = new Uploader();
104 |
105 | $files = $this->uploader->filterIgnoredFiles($uploadFiles, $this->options['ignored']);
106 | $uploadFiles = array_unique($files['upload']);
107 | $ignoredFiles = array_unique($files['ignored']);
108 |
109 | return view('gitup::preview', compact('uploadFiles', 'deleteFiles', 'ignoredFiles', 'servers', 'commits'));
110 | }
111 |
112 | public function uploadFiles()
113 | {
114 | error_reporting(1);
115 | set_time_limit(0);
116 |
117 | ini_set('output_buffering', false);
118 | ini_set('implicit_flush', 'true');
119 |
120 | $files = [];
121 | $uploadFiles = [];
122 | $deleteFiles = [];
123 |
124 | if (!count(request()->server_name)) {
125 | $this->error('No server specified!');
126 | exit;
127 | }
128 |
129 | // output
130 | echo '
';
131 | echo '';
132 | echo '';
133 | echo '';
134 |
135 | $this->out('
Deployment started...');
136 | echo '
';
137 |
138 | foreach (request()->server_name as $server) {
139 |
140 | if (!$server) {
141 | continue;
142 | }
143 |
144 | $options = config('gitup.servers.' . $server);
145 |
146 | // check to make sure we are good to go
147 | $this->checkUp($options);
148 |
149 | /*
150 | * Git Status Codes
151 | *
152 | * A: addition of a file
153 | * C: copy of a file into a new one
154 | * D: deletion of a file
155 | * M: modification of the contents or mode of a file
156 | * R: renaming of a file
157 | * T: change in the type of the file
158 | * U: file is unmerged (you must complete the merge before it can be committed)
159 | * X: "unknown" change type (most probably a bug, please report it)
160 | */
161 |
162 | foreach (request()->commits as $key => $commit) {
163 | $files[] = GitUp::getFiles($commit);
164 | }
165 |
166 | foreach ($files as $fileArray) {
167 | foreach ($fileArray as $file) {
168 | $type = current($file);
169 | $path = next($file);
170 |
171 | if (!trim($path) || $path == '.' || $path == '..') {
172 | continue;
173 | }
174 |
175 | if ($type && $path) {
176 | if ($type === 'A' || $type === 'C' || $type === 'M' || $type === 'T') {
177 | $uploadFiles[] = $path;
178 | } elseif ($type === 'D') {
179 | $deleteFiles[] = $path;
180 | }
181 | }
182 | }
183 | }
184 |
185 | // do not upload excluded files
186 | $this->uploader = new Uploader();
187 | $this->uploader->setOptions($options);
188 |
189 | $uploadFiles = $this->uploader->filterIgnoredFiles($uploadFiles, $this->options['ignored'])['upload'];
190 |
191 | if (!$uploadFiles) {
192 | $this->out('Nothing to Upload');
193 | exit;
194 | }
195 |
196 | $connector = $this->uploader->getConnector();
197 |
198 | try {
199 |
200 | $this->out('Server: ' . '
' . ucfirst($server) . '');
201 |
202 | $this->out('Zipping files...');
203 |
204 | $result = $this->uploader->createZipOfChangedFiles($uploadFiles);
205 |
206 | if ($result === false) {
207 | $this->error('Could not create zip file to upload :(');
208 | exit;
209 | }
210 |
211 | $this->out('Zip file created...');
212 |
213 | $this->out('Connecting to server...');
214 |
215 | $connector->connect($options);
216 |
217 | $this->out('Uploading extract files script...');
218 |
219 | $uploadStatus = $connector->upload($this->uploader->extractScriptFile, $options['public_path']);
220 |
221 | if (!$uploadStatus) {
222 | $this->error('Could not upload script file.');
223 | exit;
224 | }
225 |
226 | $this->out('Uploading zip file...');
227 |
228 | $uploadStatus = $connector->upload($this->uploader->zipFile, '/');
229 |
230 | if (!$uploadStatus) {
231 | $this->error('Could not upload zip file.');
232 | exit;
233 | }
234 |
235 | $this->out('Extracting files on server...');
236 |
237 | $hitUrl = $options['domain'] . $options['public_path'] . '/' . basename($this->uploader->extractScriptFile);
238 | $response = file_get_contents($hitUrl);
239 |
240 | if ($response === 'ok') {
241 |
242 | $this->out('Files uploaded successfully...');
243 |
244 | // delete files deleted in commits
245 | if ($deleteFiles) {
246 | foreach ($deleteFiles as $file) {
247 | $deleteStatus = $connector->deleteAt($file);
248 |
249 | if ($deleteStatus === true) {
250 | $this->out('Deleted: ' . $file);
251 | } else {
252 | $this->error("Could not delete '$file'");
253 | }
254 | }
255 | }
256 |
257 | $this->out('Finishing, please wait...');
258 |
259 | // delete script file
260 | $connector->deleteAt($options['public_path'] . $this->uploader->extractScriptFile);
261 |
262 | // delete deployment file
263 | $connector->delete(basename($this->uploader->zipFile));
264 |
265 | // save data in db too
266 | $commits = GitUp::commitsData(request()->commits);
267 |
268 | foreach ($commits as $commit) {
269 |
270 | $uploadFiles = [];
271 | $deleteFiles = [];
272 |
273 | $files = GitUp::getFiles($commit['commit_id']);
274 |
275 | foreach ($files as $fileArray) {
276 | $type = current($fileArray);
277 | $path = next($fileArray);
278 |
279 | if (!trim($path) || $path == '.' || $path == '..') {
280 | continue;
281 | }
282 |
283 | if ($type && $path) {
284 | if ($type === 'A' || $type === 'C' || $type === 'M' || $type === 'T') {
285 | $uploadFiles[] = $path;
286 | } elseif ($type === 'D') {
287 | $deleteFiles[] = $path;
288 | }
289 | }
290 | }
291 |
292 | $uploadFiles = array_unique($uploadFiles);
293 | $deleteFiles = array_unique($deleteFiles);
294 |
295 | $files = [
296 | 'upload' => $uploadFiles,
297 | 'delete' => $deleteFiles,
298 | ];
299 |
300 | $rExists = DB::table('commits')
301 | ->where('commit_id', $commit['commit_id'])
302 | ->where('server', $server)
303 | ->first();
304 |
305 | if (!$rExists) {
306 | DB::table('commits')->insert([
307 | 'user' => $commit['user'],
308 | 'commit_id' => $commit['commit_id'],
309 | 'server' => $server,
310 | 'message' => $commit['message'],
311 | 'files' => json_encode($files),
312 | 'created_at' => date('Y-m-d H:i:s'),
313 | 'updated_at' => date('Y-m-d H:i:s'),
314 | ]);
315 | }
316 | }
317 |
318 | echo '
';
319 |
320 | // to avoid too many connections error
321 | $connector->disconnect();
322 |
323 | } else {
324 | $this->error('Error: Unable to extract files.');
325 | }
326 |
327 | } catch (\Exception $e) {
328 | $this->error($e->getMessage());
329 | $connector->disconnect();
330 | echo '';
331 | }
332 | }
333 |
334 | $this->out('
Deployment finished :)');
335 |
336 | echo '
';
337 | echo '
← Back';
338 | echo '';
339 | echo '';
340 |
341 | $connector->disconnect();
342 | }
343 |
344 | protected function out($message)
345 | {
346 | $arrow = '✔';
347 |
348 | echo "$arrow $message
";
349 |
350 | ob_flush();
351 | flush();
352 | sleep(1);
353 | }
354 |
355 | protected function error($message)
356 | {
357 | echo "
$message";
358 |
359 | ob_flush();
360 | flush();
361 | sleep(1);
362 | }
363 |
364 | /**
365 | * Checks if everything is okay before we proceed
366 | *
367 | * @param $options
368 | */
369 | protected function checkUp($options)
370 | {
371 | $dir = dirname(getcwd());
372 |
373 | if (!isset($options['connector'])) {
374 | $this->error('Connector is not specified in config file!');
375 | exit;
376 | }
377 |
378 | if (!file_exists("$dir/.git")) {
379 | $this->error("'$dir' is not a Git repository");
380 | exit;
381 | }
382 | }
383 |
384 | public function statistics()
385 | {
386 | $commits = collect(GitUp::commitsAll())->groupBy('user');
387 |
388 | return view('gitup::statistics', compact('commits'));
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/src/Http/routes.php:
--------------------------------------------------------------------------------
1 | 'Sarfraznawaz2005\GitUp\Http\Controllers',
6 | 'prefix' => config('gitup.route', 'gitup')
7 | ],
8 | function () {
9 | Route::get('/', 'GitUpController@index')->name('__gitup__');
10 |
11 | Route::get('commits', 'GitUpController@getCommits')->name('gitup_commits');
12 | Route::get('files/{commitid?}', 'GitUpController@getFiles')->name('gitup_files');
13 |
14 | Route::post('preview', 'GitUpController@previewUploadFiles')->name('gitup_preview');
15 | Route::post('upload', 'GitUpController@uploadFiles')->name('gitup_upload');
16 |
17 | Route::get('statistics', 'GitUpController@statistics')->name('gitup_statistics');
18 | }
19 | );
20 |
--------------------------------------------------------------------------------
/src/Migrations/2016_09_12_99999_create_commits_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('commit_id');
19 | $table->string('server');
20 | $table->string('message');
21 | $table->string('user');
22 | $table->text('files');
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::drop('commits');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Uploader/Connectors/FTP.php:
--------------------------------------------------------------------------------
1 | connector = new Filesystem(new FtpAdapter($this->getOptions($options)));
26 | } catch (\Exception $e) {
27 | echo "\r\nError: {$e->getMessage()}\r\n";
28 | exit;
29 | }
30 | }
31 |
32 | function upload($path, $destination, $overwrite = true)
33 | {
34 | $destination = $destination . '/' . basename($path);
35 | $destination = str_replace('//', '/', $destination);
36 |
37 | if ($overwrite && $this->existsAt($destination)) {
38 | $this->deleteAt($destination);
39 | }
40 |
41 | $stream = fopen($path, 'r+');
42 | $result = $this->connector->writeStream($destination, $stream);
43 | fclose($stream);
44 |
45 | return $result;
46 | }
47 |
48 | function exists($path)
49 | {
50 | return $this->connector->has(basename($path));
51 | }
52 |
53 | function existsAt($path)
54 | {
55 | return $this->connector->has($path);
56 | }
57 |
58 | function delete($path)
59 | {
60 | $this->deleteAt(basename($path));
61 | }
62 |
63 | function deleteAt($path)
64 | {
65 | try {
66 | if (!is_dir($path)) {
67 | return $this->connector->delete($path);
68 | } else {
69 | return $this->connector->deleteDir($path);
70 | }
71 | } catch (\Exception $e) {
72 | return $e->getMessage();
73 | }
74 | }
75 |
76 | function write($path, $contents, $overwrite = true)
77 | {
78 | if ($overwrite && $this->exists($path)) {
79 | $this->delete($path);
80 | }
81 |
82 | return $this->connector->write(basename($path), $contents);
83 | }
84 |
85 | function read($path)
86 | {
87 | return $this->connector->read(basename($path));
88 | }
89 |
90 | function disconnect()
91 | {
92 | $this->connector->getAdapter()->disconnect();
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Uploader/Connectors/SFTP.php:
--------------------------------------------------------------------------------
1 | getOptions($options);
23 |
24 | // see if key file exists
25 | if (!trim($options['key_file']) || !is_file($options['key_file'])) {
26 | throw new \Exception("Private key file: {$options['key_file']} doesn't exists.");
27 | }
28 |
29 | $options['privateKey'] = $options['key_file'];
30 |
31 | if (!trim($options['port']) || $options['port'] == 21) {
32 | $options['port'] = 22;
33 | }
34 |
35 | $this->connector = new Filesystem(new SftpAdapter($options));
36 |
37 | } catch (\Exception $e) {
38 | echo "\r\nError: {$e->getMessage()}\r\n";
39 | exit;
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Uploader/Contracts/ConnectorInterface.php:
--------------------------------------------------------------------------------
1 | '',
18 | 'username' => '',
19 | 'password' => '',
20 | 'root' => '/',
21 | 'port' => null,
22 | 'passive' => null,
23 | 'timeout' => null,
24 | 'ssl' => false,
25 | ];
26 |
27 | if (is_array($options)) {
28 | $options = array_merge($defaults, $options);
29 | }
30 |
31 | // defaults
32 | $options['passive'] = ($options['passive'] ?: true);
33 | $options['ssl'] = ($options['ssl'] ?: false);
34 | $options['port'] = ($options['port'] ?: 21);
35 |
36 | $this->validateOptions($options);
37 |
38 | $options['root'] = $this->addSlashIfMissing($options['root']);
39 | $options['domain'] = $this->addSlashIfMissing($options['domain']);
40 | $options['public_path'] = $this->addSlashIfMissing($options['public_path']);
41 |
42 | return $options;
43 | }
44 |
45 | private function addSlashIfMissing($path)
46 | {
47 | if (substr($path, -1, 1) !== '/') {
48 | $path = $path . '/';
49 | }
50 |
51 | return $path;
52 | }
53 |
54 | /**
55 | * Validates important options
56 | *
57 | * @param array $options
58 | * @throws \Exception
59 | */
60 | protected function validateOptions(array $options)
61 | {
62 | if (!trim($options['root'])) {
63 | throw new \Exception('"root" option not specified!');
64 | }
65 |
66 | if (!trim($options['public_path'])) {
67 | throw new \Exception('"public_path" option not specified!');
68 | }
69 |
70 | if (!trim($options['domain'])) {
71 | throw new \Exception('"domain" option not specified!');
72 | }
73 |
74 | if (!filter_var($options['domain'], FILTER_VALIDATE_URL)) {
75 | throw new \Exception('Invalid value for "domain" option!');
76 | }
77 |
78 | }
79 | }
--------------------------------------------------------------------------------
/src/Uploader/Uploader.php:
--------------------------------------------------------------------------------
1 | exportFolder = $this->tmpDir() . $this->exportFolder;
28 | $this->zipFile = $this->tmpDir() . $this->zipFile;
29 | $this->extractScriptFile = $this->tmpDir() . $this->extractScriptFile;
30 |
31 | $this->zipFile = str_replace('\\', '/', $this->zipFile);
32 | $this->exportFolder = str_replace('\\', '/', $this->exportFolder);
33 | $this->extractScriptFile = str_replace('\\', '/', $this->extractScriptFile);
34 | }
35 |
36 | public function setOptions(array $options = [])
37 | {
38 | $this->options = $options;
39 | }
40 |
41 | public function getConnector()
42 | {
43 | if ($this->options['connector'] === 'FTP') {
44 | $connector = new FTP();
45 | } elseif ($this->options['connector'] === 'SFTP') {
46 | $connector = new SFTP();
47 | }
48 |
49 | return $connector;
50 | }
51 |
52 | public function createZipOfChangedFiles($files)
53 | {
54 | @unlink($this->zipFile);
55 | @unlink($this->extractScriptFile);
56 | @$this->recursiveRmDir($this->exportFolder);
57 |
58 | file_put_contents($this->extractScriptFile, $this->extractScript($this->options['root']));
59 |
60 | @mkdir($this->exportFolder, 0777, true);
61 |
62 | // remove duplicates
63 | $files = array_unique($files);
64 |
65 | foreach ($files as $file) {
66 | $folder = $this->exportFolder . DIRECTORY_SEPARATOR . dirname($file);
67 |
68 | if (!file_exists($folder)) {
69 | @mkdir($folder, 0777, true);
70 | }
71 |
72 | $file = str_replace('\\', '/', $file);
73 |
74 | $source = getcwd() . '/' . $file;
75 | $source = str_replace('public/public/', 'public/', $source);
76 |
77 | if (!file_exists($source)) {
78 | $source = str_replace('public/', '', $source);
79 | }
80 |
81 | $desination = $this->exportFolder . DIRECTORY_SEPARATOR . $file;
82 |
83 | $desination = str_replace('\\', '/', $desination);
84 | $source = str_replace('\\', '/', $source);
85 |
86 | if (!copy($source, $desination)) {
87 | echo 'Could not copy: ' . $desination;
88 | }
89 | }
90 |
91 | // remove those files from export folder which are excluded
92 | $iterator = new RecursiveIteratorIterator(
93 | new \RecursiveDirectoryIterator($this->exportFolder,
94 | \FilesystemIterator::SKIP_DOTS),
95 | \RecursiveIteratorIterator::CHILD_FIRST
96 | );
97 |
98 | foreach ($iterator as $filename => $fileInfo) {
99 | $pathArray = explode($this->exportFolder, $filename);
100 | $currentFile = trim($pathArray[1], '\\');
101 | $currentFile = str_replace('\\', '/', $currentFile);
102 |
103 | if (in_array(trim($currentFile), $files)) {
104 | continue;
105 | }
106 |
107 | if (!$fileInfo->isDir()) {
108 | @unlink($filename);
109 | }
110 | }
111 |
112 | // now create zip file of these files
113 | $this->zipData($this->exportFolder, $this->zipFile);
114 |
115 | $this->recursiveRmDir($this->exportFolder);
116 |
117 | if (!file_exists($this->zipFile)) {
118 | return false;
119 | }
120 | }
121 |
122 | protected function extractScript($root)
123 | {
124 | $userRoot = $root;
125 | $zipFile = basename($this->zipFile);
126 |
127 | return <<< SCRIPT
128 | open("\$root/$zipFile");
139 |
140 | if (\$res === true){
141 | \$zip->extractTo("\$root");
142 | \$zip->close();
143 | echo 'ok';
144 | } else {
145 | echo 'failed';
146 | }
147 |
148 | SCRIPT;
149 |
150 | }
151 |
152 | protected function recursiveRmDir($dir)
153 | {
154 | if (!file_exists($dir) || !is_dir($dir)) {
155 | return false;
156 | }
157 |
158 | $iterator = new RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
159 | \RecursiveIteratorIterator::CHILD_FIRST);
160 |
161 | foreach ($iterator as $filename => $fileInfo) {
162 | if ($fileInfo->isDir()) {
163 | rmdir($filename);
164 | } else {
165 | unlink($filename);
166 | }
167 | }
168 |
169 | @rmdir($dir);
170 | }
171 |
172 | protected function zipData($source, $destination)
173 | {
174 | if (!extension_loaded('zip') || !file_exists($source)) {
175 | return false;
176 | }
177 |
178 | $zip = new ZipArchive();
179 |
180 | if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
181 | return false;
182 | }
183 |
184 | $source = str_replace('\\', '/', realpath($source));
185 |
186 | if (is_dir($source) === true) {
187 | $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source),
188 | RecursiveIteratorIterator::SELF_FIRST);
189 |
190 | foreach ($files as $file) {
191 | $file = str_replace('\\', '/', $file);
192 |
193 | // Ignore "." and ".." folders
194 | if (in_array(substr($file, strrpos($file, '/') + 1), array('.', '..'))) {
195 | continue;
196 | }
197 |
198 | if (is_dir($file) === true) {
199 | $zip->addEmptyDir(str_replace($source . '/', '', $file));
200 | } else {
201 | if (is_file($file) === true) {
202 |
203 | $str1 = str_replace($source . '/', '', '/' . $file);
204 | $zip->addFromString($str1, file_get_contents($file));
205 |
206 | }
207 | }
208 | }
209 | } else {
210 | if (is_file($source) === true) {
211 | $zip->addFromString(basename($source), file_get_contents($source));
212 | }
213 | }
214 |
215 | return $zip->close();
216 | }
217 |
218 | /**
219 | * Filter ignore files.
220 | *
221 | * @param array $files Array of files which needed to be filtered
222 | *
223 | * @param $ignoredPatterns
224 | * @return array with `files` (filtered) and `filesToSkip`
225 | */
226 | public function filterIgnoredFiles($files, $ignoredPatterns)
227 | {
228 | $filesToSkip = [];
229 |
230 | $files = array_map(function ($file) {
231 | return str_replace('\\', '/', $file);
232 | }, $files);
233 |
234 | foreach ($files as $i => $file) {
235 | foreach ($ignoredPatterns as $pattern) {
236 | if ($this->patternMatch($pattern, $file)) {
237 | unset($files[$i]);
238 | $filesToSkip[] = $file;
239 | break;
240 | }
241 | }
242 | }
243 |
244 | $files = array_values($files);
245 |
246 | return ['upload' => $files, 'ignored' => $filesToSkip];
247 | }
248 |
249 | /**
250 | * Glob the file path.
251 | *
252 | * @param string $pattern
253 | * @param string $string
254 | *
255 | * @return string
256 | */
257 | protected function patternMatch($pattern, $string)
258 | {
259 | return preg_match('#^' . strtr(preg_quote($pattern, '#'), ['\*' => '.*', '\?' => '.']) . '$#i', $string);
260 | }
261 |
262 | protected function tmpDir()
263 | {
264 | $dir = sys_get_temp_dir();
265 | $dir = trim($dir);
266 |
267 | if ($dir && substr($dir, -1, 1) !== '/') {
268 | $dir = $dir . '/';
269 | return $dir;
270 | }
271 |
272 | return '';
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/Views/files.blade.php:
--------------------------------------------------------------------------------
1 | @extends('gitup::layout.gitup')
2 |
3 | @section('title')
4 |
5 | Back
6 |
7 | @endsection
8 |
9 | @section('content')
10 |
11 |
12 |
13 | Status |
14 | File |
15 |
16 |
17 |
18 |
19 |
20 | Status |
21 | |
22 |
23 |
24 |
25 |
26 | @foreach($files as $file)
27 |
28 | @if (! trim($file['file']))
29 | @continue
30 | @endif
31 |
32 |
43 |
44 |
45 | {{$file['status']}}
46 | |
47 | {{$file['file']}} |
48 |
49 | @endforeach
50 |
51 |
52 |
53 |
54 |
55 |
61 | @endsection
62 |
63 | @push('styles')
64 |
70 | @endpush
71 |
72 | @push('scripts')
73 |
112 | @endpush
--------------------------------------------------------------------------------
/src/Views/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('gitup::layout.gitup')
2 |
3 | @section('title', 'Commits')
4 |
5 | @section('header')
6 | @if ($isDirty)
7 |
8 | View Un-Committed Files
9 |
10 | @else
11 |
CLEAN
12 | @endif
13 | @endsection
14 |
15 | @section('content')
16 |
17 |
117 | @endsection
118 |
119 | @push('scripts')
120 |
219 | @endpush
220 |
--------------------------------------------------------------------------------
/src/Views/layout/gitup.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
GitUp
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
104 |
105 | @stack('styles')
106 |
107 |
108 |
109 |
110 |
128 |
129 |
130 |
131 |
132 |
140 |
141 | @yield('content')
142 |
143 |
144 |
145 |
146 |
147 |
149 |
154 |
159 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
174 |
175 | @stack('scripts')
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/src/Views/preview.blade.php:
--------------------------------------------------------------------------------
1 | @extends('gitup::layout.gitup')
2 |
3 | @section('title')
4 |
5 | ← Back
6 |
7 | @endsection
8 |
9 | @section('content')
10 |
11 |
122 |
123 | @endsection
124 |
125 | @push('scripts')
126 |
134 | @endpush
135 |
--------------------------------------------------------------------------------
/src/Views/statistics.blade.php:
--------------------------------------------------------------------------------
1 | @extends('gitup::layout.gitup')
2 |
3 | @section('title', 'Statistics')
4 |
5 | @section('content')
6 |
7 | @endsection
8 |
9 | @push('styles')
10 |
16 | @endpush
17 |
18 | @push('scripts')
19 |
20 |
51 | @endpush
--------------------------------------------------------------------------------