Showing 3 changed files with 606 additions and 22 deletions
+2 -2
lib/Gitprep.pm
... ...
@@ -10,7 +10,7 @@ use Validator::Custom;
10 10
 use Encode qw/encode decode/;
11 11
 use Gitprep::API;
12 12
 use Carp 'croak';
13
-use Gitprep::RepManager;
13
+use Gitprep::Manager;
14 14
 use Scalar::Util 'weaken';
15 15
 use Carp 'croak';
16 16
 
... ...
@@ -50,7 +50,7 @@ sub startup {
50 50
   $git->bin($git_bin);
51 51
   
52 52
   # Repository Manager
53
-  my $manager = Gitprep::RepManager->new(app => $self);
53
+  my $manager = Gitprep::Manager->new(app => $self);
54 54
   weaken $manager->{app};
55 55
   $self->manager($manager);
56 56
   
+581
lib/Gitprep/Manager.pm
... ...
@@ -0,0 +1,581 @@
1
+package Gitprep::Manager;
2
+use Mojo::Base -base;
3
+
4
+use Carp 'croak';
5
+use File::Copy 'move';
6
+use File::Path qw/mkpath rmtree/;
7
+use File::Temp ();
8
+use Encode 'encode';
9
+
10
+has 'app';
11
+
12
+sub default_branch {
13
+  my ($self, $user, $project) = @_;
14
+  
15
+  my $default_branch = $self->app->dbi->model('project')
16
+    ->select('default_branch', id => [$user, $project])
17
+    ->value;
18
+  
19
+  return $default_branch;
20
+}
21
+
22
+sub members {
23
+  my ($self, $user, $project) = @_;
24
+  
25
+  # DBI
26
+  my $dbi = $self->app->dbi;
27
+  
28
+  # Original project id
29
+  my $original_pid = $dbi->model('project')
30
+    ->select('original_pid', id => [$user, $project])->value;
31
+  
32
+  # Members
33
+  my $members = $dbi->model('project')->select(
34
+    ['user_id as id', 'name as project'],
35
+    where => [
36
+      ['and',
37
+        ':original_pid{=}',
38
+        ['or', ':user_id{<>}', ':name{<>}']
39
+      ],
40
+      {
41
+        original_pid => $original_pid,
42
+        user_id => $user,
43
+        name => $project
44
+      }
45
+    ],
46
+    append => 'order by user_id, name'
47
+  )->all;
48
+
49
+  return $members;
50
+}
51
+
52
+sub create_project {
53
+  my ($self, $user, $project, $opts) = @_;
54
+  
55
+  my $dbi = $self->app->dbi;
56
+  
57
+  # Create project
58
+  my $error;
59
+  eval {
60
+    $dbi->connector->txn(sub {
61
+      eval { $self->_create_project($user, $project) };
62
+      croak $error = $@ if $@;
63
+      eval {$self->_create_rep($user, $project, $opts) };
64
+      croak $error = $@ if $@;
65
+    });
66
+  };
67
+  croak $error if $@;
68
+}
69
+
70
+sub create_user {
71
+  my ($self, $user, $data) = @_;
72
+
73
+  my $dbi = $self->app->dbi;
74
+  
75
+  # Create user
76
+  my $error;
77
+  eval {
78
+    $dbi->connector->txn(sub {
79
+      eval { $self->_create_db_user($user, $data) };
80
+      croak $error = $@ if $@;
81
+      eval {$self->_create_user_dir($user) };
82
+      croak $error = $@ if $@;
83
+    });
84
+  };
85
+  croak $error if $@;
86
+}
87
+
88
+sub delete_project {
89
+  my ($self, $user, $project) = @_;
90
+  
91
+  my $dbi = $self->app->dbi;
92
+  
93
+  # Delete project
94
+  my $error;
95
+  eval {
96
+    $dbi->connector->txn(sub {
97
+      eval { $self->_delete_project($user, $project) };
98
+      croak $error = $@ if $@;
99
+      eval {$self->_delete_rep($user, $project) };
100
+      croak $error = $@ if $@;
101
+    });
102
+  };
103
+  croak $error if $@;
104
+  
105
+  return 1;
106
+}
107
+
108
+sub delete_user {
109
+  my ($self, $user) = @_;
110
+  
111
+  my $dbi = $self->app->dbi;
112
+  
113
+  # Delete user
114
+  my $error;
115
+  eval {
116
+    $dbi->connector->txn(sub {
117
+      eval { $self->_delete_db_user($user) };
118
+      croak $error = $@ if $@;
119
+      eval {$self->_delete_user_dir($user) };
120
+      croak $error = $@ if $@;
121
+    });
122
+  };
123
+  croak $error if $@;
124
+  
125
+  return 1;
126
+}
127
+
128
+sub original_project {
129
+  my ($self, $user, $project) = @_;
130
+  
131
+  my $dbi = $self->app->dbi;
132
+  
133
+  my $original_project = $dbi->model('project')
134
+    ->select('original_project', id => [$user, $project])
135
+    ->value;
136
+  return unless defined $original_project && length $original_project;
137
+  
138
+  return $original_project;
139
+}
140
+
141
+sub original_user {
142
+  my ($self, $user, $project) = @_;
143
+  
144
+  my $dbi = $self->app->dbi;
145
+  
146
+  my $original_user = $dbi->model('project')
147
+    ->select('original_user', id => [$user, $project])
148
+    ->value;
149
+  return unless defined $original_user && length $original_user;
150
+  
151
+  return $original_user;
152
+}
153
+
154
+sub _delete_db_user {
155
+  my ($self, $user) = @_;
156
+  
157
+  $self->app->dbi->model('user')->delete(id => $user);
158
+}
159
+
160
+sub _delete_user_dir {
161
+  my ($self, $user) = @_;
162
+
163
+  my $home = $self->app->git->rep_home;
164
+  my $user_dir = "$home/$user";
165
+  rmtree $user_dir;
166
+}
167
+
168
+sub _create_db_user {
169
+  my ($self, $user, $data) = @_;
170
+  
171
+  $self->app->dbi->model('user')->insert($data, id => $user);
172
+}
173
+
174
+sub _create_user_dir {
175
+  my ($self, $user) = @_;
176
+
177
+  my $home = $self->app->git->rep_home;
178
+  my $user_dir = "$home/$user";
179
+  mkpath $user_dir;
180
+}
181
+
182
+sub exists_project { shift->_exists_project(@_) }
183
+
184
+sub fork_project {
185
+  my ($self, $user, $original_user, $project) = @_;
186
+  
187
+  # DBI
188
+  my $dbi = $self->app->dbi;
189
+  
190
+  # Fork project
191
+  my $error;
192
+  eval {
193
+    $dbi->connector->txn(sub {
194
+      
195
+      # Original project id
196
+      my $original_pid = $dbi->model('project')
197
+        ->select('original_pid', id => [$original_user, $project])->value;
198
+      
199
+      croak "Can't get original project id"
200
+        unless defined $original_pid && $original_pid > 0;
201
+      
202
+      # Create project
203
+      eval {
204
+        $self->_create_project(
205
+          $user,
206
+          $project,
207
+          {
208
+            original_user => $original_user,
209
+            original_project => $project,
210
+            original_pid => $original_pid
211
+          }
212
+        );
213
+      };
214
+      croak $error = $@ if $@;
215
+      
216
+      # Create repository
217
+      eval {
218
+        $self->_fork_rep($original_user, $project, $user, $project);
219
+      };
220
+      croak $error = $@ if $@;
221
+    });
222
+  };
223
+  croak $error if $@;
224
+}
225
+
226
+sub _fork_rep {
227
+  my ($self, $user, $project, $to_user, $to_project) = @_;
228
+  
229
+  # Git
230
+  my $git = $self->app->git;
231
+  
232
+  # Git clone
233
+  my $rep = $git->rep($user, $project);
234
+  my $to_rep = $git->rep($to_user, $to_project);
235
+  my @git_clone_cmd = (
236
+    $git->bin,
237
+    'clone',
238
+    '-q',
239
+    '--bare',
240
+    $rep,
241
+    $to_rep
242
+  );
243
+  system(@git_clone_cmd) == 0
244
+    or croak "Can't execute git clone";
245
+}
246
+
247
+sub rename_project {
248
+  my ($self, $user, $project, $renamed_project) = @_;
249
+  
250
+  my $git = $self->app->git;
251
+  my $dbi = $self->app->dbi;
252
+  
253
+  my $error = {};
254
+  
255
+  if ($self->_exists_project($user, $renamed_project)
256
+    || $self->_exists_rep($user, $renamed_project))
257
+  {
258
+    $error->{message} = 'Already exists';
259
+    return $error;
260
+  }
261
+  else {
262
+    $dbi->connector->txn(sub {
263
+      $self->_rename_project($user, $project, $renamed_project);
264
+      $self->_rename_rep($user, $project, $renamed_project);
265
+    });
266
+    if ($@) {
267
+      $error->{message} = 'Rename failed';
268
+      return $error;
269
+    }
270
+  }
271
+  
272
+  return 1;
273
+}
274
+
275
+sub setup_database {
276
+  my $self = shift;
277
+  
278
+  my $dbi = $self->app->dbi;
279
+  
280
+  # Create user table
281
+  eval {
282
+    my $sql = <<"EOS";
283
+create table user (
284
+  row_id integer primary key autoincrement,
285
+  id not null unique default ''
286
+);
287
+EOS
288
+    $dbi->execute($sql);
289
+  };
290
+
291
+  # Create usert columns
292
+  my $user_columns = [
293
+    "admin not null default '0'",
294
+    "password not null default ''",
295
+    "salt not null default ''"
296
+  ];
297
+  for my $column (@$user_columns) {
298
+    eval { $dbi->execute("alter table user add column $column") };
299
+  }
300
+  
301
+  # Check user table
302
+  eval { $dbi->select([qw/row_id id admin password salt/], table => 'user') };
303
+  if ($@) {
304
+    my $error = "Can't create user table properly: $@";
305
+    $self->app->log->error($error);
306
+    croak $error;
307
+  }
308
+  
309
+  # Create project table
310
+  eval {
311
+    my $sql = <<"EOS";
312
+create table project (
313
+  row_id integer primary key autoincrement,
314
+  user_id not null,
315
+  name not null,
316
+  unique(user_id, name)
317
+);
318
+EOS
319
+    $dbi->execute($sql);
320
+  };
321
+  
322
+  # Create Project columns
323
+  my $project_columns = [
324
+    "default_branch not null default 'master'",
325
+    "original_user not null default ''",
326
+    "original_project not null default ''",
327
+    "original_pid integer not null default 0"
328
+  ];
329
+  for my $column (@$project_columns) {
330
+    eval { $dbi->execute("alter table project add column $column") };
331
+  }
332
+
333
+  # Check project table
334
+  eval { $dbi->select([qw/default_branch original_user original_project original_pid/], table => 'project') };
335
+  if ($@) {
336
+    my $error = "Can't create project table properly: $@";
337
+    $self->app->log->error($error);
338
+    croak $error;
339
+  }
340
+
341
+  # Create number table
342
+  eval {
343
+    my $sql = <<"EOS";
344
+create table number (
345
+  row_id integer primary key autoincrement,
346
+  key not null unique
347
+);
348
+EOS
349
+    $dbi->execute($sql);
350
+  };
351
+  
352
+  # Create number columns
353
+  my $number_columns = [
354
+    "value integer not null default '0'"
355
+  ];
356
+  for my $column (@$number_columns) {
357
+    eval { $dbi->execute("alter table number add column $column") };
358
+  }
359
+
360
+  # Check number table
361
+  eval { $dbi->select([qw/row_id key value/], table => 'number') };
362
+  if ($@) {
363
+    my $error = "Can't create number table properly: $@";
364
+    $self->app->log->error($error);
365
+    croak $error;
366
+  }
367
+  
368
+  # Original project id numbert
369
+  eval { $dbi->insert({key => 'original_pid'}, table => 'number') };
370
+  my $original_pid = $dbi->select(
371
+    'key',
372
+    table => 'number',
373
+    where => {key => 'original_pid'}
374
+  )->value;
375
+  unless (defined $original_pid) {
376
+    my $error = "Can't create original_pid row in number table";
377
+    $self->app->log->error($error);
378
+    croak $error;
379
+  }
380
+}
381
+
382
+sub _create_project {
383
+  my ($self, $user, $project, $params) = @_;
384
+  $params ||= {};
385
+  
386
+  # DBI
387
+  my $dbi = $self->app->dbi;
388
+  
389
+  # Create project
390
+  $dbi->connector->txn(sub {
391
+    unless (defined $params->{original_pid}) {
392
+      my $number = $dbi->model('number')->select('value', where => {key => 'original_pid'})->value;
393
+      $number++;
394
+      $dbi->model('number')->update({value => $number}, where => {key => 'original_pid'});
395
+      $params->{original_pid} = $number;
396
+    }
397
+    $dbi->model('project')->insert($params, id => [$user, $project]);
398
+  });
399
+}
400
+
401
+sub _create_rep {
402
+  my ($self, $user, $project, $opts) = @_;
403
+  
404
+  # Git
405
+  my $git = $self->app->git;
406
+  
407
+  # Create repository directory
408
+  my $rep = $git->rep($user, $project);
409
+  mkdir $rep
410
+    or croak "Can't create directory $rep: $!";
411
+
412
+  eval {
413
+    # Git init
414
+    {
415
+      my @git_init_cmd = $git->cmd_rep($rep, 'init', '--bare');
416
+      open my $fh, "-|", @git_init_cmd
417
+        or croak  "Can't execute git init";
418
+      close $fh;
419
+    }
420
+    
421
+    # Add git-daemon-export-ok
422
+    {
423
+      my $file = "$rep/git-daemon-export-ok";
424
+      open my $fh, '>', $file
425
+        or croak "Can't create git-daemon-export-ok: $!"
426
+    }
427
+    
428
+    # HTTP support
429
+    my @git_update_server_info_cmd = $git->cmd_rep(
430
+      $rep,
431
+      '--bare',
432
+      'update-server-info'
433
+    );
434
+    system(@git_update_server_info_cmd) == 0
435
+      or croak "Can't execute git --bare update-server-info";
436
+    move("$rep/hooks/post-update.sample", "$rep/hooks/post-update")
437
+      or croak "Can't move post-update";
438
+    
439
+    # Description
440
+    {
441
+      my $description = $opts->{description};
442
+      $description = '' unless defined $description;
443
+      my $file = "$rep/description";
444
+      open my $fh, '>', $file
445
+        or croak "Can't open $file: $!";
446
+      print $fh encode('UTF-8', $description)
447
+        or croak "Can't write $file: $!";
448
+      close $fh;
449
+    }
450
+    
451
+    # Add README and commit
452
+    if ($opts->{readme}) {
453
+      # Create working directory
454
+      my $temp_dir =  File::Temp->newdir;
455
+      my $temp_work = "$temp_dir/work";
456
+      mkdir $temp_work
457
+        or croak "Can't create directory $temp_work: $!";
458
+
459
+      # Git init
460
+      my @git_init_cmd = $git->cmd_rep($temp_work, 'init', '-q');
461
+      system(@git_init_cmd) == 0
462
+        or croak "Can't execute git init";
463
+      
464
+      # Add README
465
+      my $file = "$temp_work/README";
466
+      open my $fh, '>', $file
467
+        or croak "Can't create $file: $!";
468
+      my @git_add_cmd = $git->cmd_rep(
469
+        $temp_work,
470
+        "--work-tree=$temp_work",
471
+        'add',
472
+        'README'
473
+      );
474
+      system(@git_add_cmd) == 0
475
+        or croak "Can't execute git add";
476
+      
477
+      # Commit
478
+      my @git_commit_cmd = $git->cmd_rep(
479
+        $temp_work,
480
+        "--work-tree=$temp_work",
481
+        'commit',
482
+        '-q',
483
+        '-m',
484
+        'first commit'
485
+      );
486
+      system(@git_commit_cmd) == 0
487
+        or croak "Can't execute git commit";
488
+      
489
+      # Push
490
+      {
491
+        my @git_push_cmd = $git->cmd_rep(
492
+          $temp_work,
493
+          "--work-tree=$temp_work",
494
+          'push',
495
+          '-q',
496
+          $rep,
497
+          'master'
498
+        );
499
+        # (This is bad, but --quiet option can't supress in old git)
500
+        my $git_push_cmd = join(' ', @git_push_cmd);
501
+        system("$git_push_cmd 2> /dev/null") == 0
502
+          or croak "Can't execute git push";
503
+      }
504
+    }
505
+  };
506
+  if ($@) {
507
+    rmtree $rep;
508
+    croak $@;
509
+  }
510
+}
511
+
512
+sub _delete_project {
513
+  my ($self, $user, $project) = @_;
514
+  
515
+  my $dbi = $self->app->dbi;
516
+  $dbi->model('project')->delete(id => [$user, $project]);
517
+}
518
+
519
+sub _delete_rep {
520
+  my ($self, $user, $project) = @_;
521
+
522
+  my $rep_home = $self->app->git->rep_home;
523
+  croak "Can't remove repository. repositry home is empty"
524
+    if !defined $rep_home || $rep_home eq '';
525
+  my $rep = "$rep_home/$user/$project.git";
526
+  rmtree $rep;
527
+  croak "Can't remove repository. repository is rest"
528
+    if -e $rep;
529
+}
530
+
531
+sub _exists_project {
532
+  my ($self, $user, $project) = @_;
533
+
534
+  my $dbi = $self->app->dbi;
535
+  my $row = $dbi->model('project')->select(id => [$user, $project])->one;
536
+  
537
+  return $row ? 1 : 0;
538
+}
539
+
540
+sub _exists_rep {
541
+  my ($self, $user, $project) = @_;
542
+  
543
+  my $rep = $self->app->git->rep($user, $project);
544
+  
545
+  return -e $rep;
546
+}
547
+
548
+sub _rename_project {
549
+  my ($self, $user, $project, $renamed_project) = @_;
550
+  
551
+  my $dbi = $self->app->dbi;
552
+  
553
+  croak "Invalid parameters"
554
+    unless defined $user && defined $project && defined $renamed_project;
555
+  
556
+  # Rename project
557
+  $dbi->model('project')->update(
558
+    {name => $renamed_project},
559
+    id => [$user, $project]
560
+  );
561
+  
562
+  # Rename related project
563
+  $dbi->model('project')->update(
564
+    {original_project => $renamed_project},
565
+    where => {original_user => $user, original_project => $project},
566
+  );
567
+}
568
+
569
+sub _rename_rep {
570
+  my ($self, $user, $project, $renamed_project) = @_;
571
+  
572
+  croak "Invalid user name or project"
573
+    unless defined $user && defined $project && defined $renamed_project;
574
+  my $rep = $self->app->git->rep($user, $project);
575
+  my $renamed_rep = $self->app->git->rep($user, $renamed_project);
576
+  
577
+  move($rep, $renamed_rep)
578
+    or croak "Can't move $rep to $renamed_rep: $!";
579
+}
580
+
581
+1;
+23 -20
templates/tags.html.ep
... ...
@@ -12,7 +12,7 @@
12 12
   
13 13
   # Ref names
14 14
   my $limit = app->config->{basic}{tags_limit};
15
-  my $page_count = 10;
15
+  my $page_count = 50;
16 16
   my $tags = $git->tags(
17 17
     $user,
18 18
     $project,
... ...
@@ -20,6 +20,7 @@
20 20
     $page_count,
21 21
     $page_count * ($page - 1)
22 22
   );
23
+  my $tags_count = $git->tags_count($user, $project);
23 24
 %>
24 25
 
25 26
 % layout 'common';
... ...
@@ -71,25 +72,27 @@
71 72
             </div>
72 73
           </div>
73 74
         % }
74
-
75
-        <ul class="pager" style="text-align:left">
76
-          % if ($page == 1) {
77
-            <li class="disabled">&laquo; Newer</li>
78
-          % } else {
79
-            % my $newer_page = $page - 1;
80
-            <li class="disable">
81
-              <a href="<%= url_for("/$user/$project/tags?page=$newer_page") %>">&laquo; Newer</a>
82
-            </li>
83
-          % }
84
-          % if (@$tags < $page_count) {
85
-            <li class="disabled">Older &raquo;</li>
86
-          % } else {
87
-            % my $older_page = $page + 1;
88
-            <li>
89
-              <a href="<%= url_for("/$user/$project/tags?page=$older_page") %>">Older &raquo;</a>
90
-            </li>
91
-          % }
92
-        </ul>
75
+        % use D;d [$page_count, $tags_count];
76
+        % if ($tags_count > $page_count) {
77
+          <ul class="pager" style="text-align:left">
78
+            % if ($page == 1) {
79
+              <li class="disabled">&laquo; Newer</li>
80
+            % } else {
81
+              % my $newer_page = $page - 1;
82
+              <li class="disable">
83
+                <a href="<%= url_for("/$user/$project/tags?page=$newer_page") %>">&laquo; Newer</a>
84
+              </li>
85
+            % }
86
+            % if (@$tags < $page_count) {
87
+              <li class="disabled">Older &raquo;</li>
88
+            % } else {
89
+              % my $older_page = $page + 1;
90
+              <li>
91
+                <a href="<%= url_for("/$user/$project/tags?page=$older_page") %>">Older &raquo;</a>
92
+              </li>
93
+            % }
94
+          </ul>
95
+        % }
93 96
       </div>
94 97
     % } else {
95 98
       <div class="well">