├── .gitignore ├── README ├── docs └── installation.rst ├── gitube ├── __init__.py ├── apps │ ├── __init__.py │ ├── account │ │ ├── __init__.py │ │ ├── urls.py │ │ └── views.py │ ├── gitweb │ │ ├── __init__.py │ │ ├── urls.py │ │ └── views.py │ ├── project │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── fixtures │ │ │ └── testing_data.json │ │ ├── forms.py │ │ ├── models.py │ │ ├── tests │ │ │ └── __init__.py │ │ ├── urls.py │ │ └── views.py │ └── sshkey │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── forms.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py ├── fixture.json ├── forms.py ├── gitweb.cgi ├── gitweb.conf.example ├── lib │ ├── __init__.py │ └── gitolite.py ├── locale │ └── zh_CN │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── localsettings.py.example ├── runfcgi.example ├── settings.py ├── static │ ├── copier.fla │ ├── copier.swf │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap.css │ │ ├── gitweb.css │ │ └── style.css │ ├── fancybox │ │ ├── blank.gif │ │ ├── fancy_close.png │ │ ├── fancy_loading.png │ │ ├── fancy_nav_left.png │ │ ├── fancy_nav_right.png │ │ ├── fancy_shadow_e.png │ │ ├── fancy_shadow_n.png │ │ ├── fancy_shadow_ne.png │ │ ├── fancy_shadow_nw.png │ │ ├── fancy_shadow_s.png │ │ ├── fancy_shadow_se.png │ │ ├── fancy_shadow_sw.png │ │ ├── fancy_shadow_w.png │ │ ├── fancy_title_left.png │ │ ├── fancy_title_main.png │ │ ├── fancy_title_over.png │ │ ├── fancy_title_right.png │ │ ├── fancybox-x.png │ │ ├── fancybox-y.png │ │ ├── fancybox.png │ │ ├── jquery.fancybox-1.3.4.css │ │ └── jquery.fancybox-1.3.4.pack.js │ ├── git-logo.png │ ├── gitweb.js │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ ├── js │ │ ├── bootstrap.min.js │ │ └── gitube.js │ └── openid-jquery │ │ ├── css │ │ └── openid.css │ │ ├── images │ │ ├── aol.gif │ │ ├── blogger.ico │ │ ├── claimid.ico │ │ ├── facebook.gif │ │ ├── flickr.ico │ │ ├── google.gif │ │ ├── livejournal.ico │ │ ├── myopenid.ico │ │ ├── openid-inputicon.gif │ │ ├── openid.gif │ │ ├── technorati.ico │ │ ├── verisign.ico │ │ ├── vidoop.ico │ │ ├── wordpress.ico │ │ └── yahoo.gif │ │ └── js │ │ └── openid-jquery.js ├── templates │ ├── account │ │ └── home.html │ ├── admin │ │ └── sshkey │ │ │ └── sshkey │ │ │ └── change_list.html │ ├── authopenid │ │ ├── associate.html │ │ ├── associate_email.txt │ │ ├── associate_email_subject.txt │ │ ├── complete.html │ │ ├── dissociate.html │ │ ├── failure.html │ │ ├── password_change_form.html │ │ └── signin.html │ ├── base.html │ ├── home.html │ ├── project │ │ ├── member_add_form.html │ │ ├── member_base.html │ │ ├── members.html │ │ ├── project_form.html │ │ ├── repository_form.html │ │ ├── view_project.html │ │ └── view_repository.html │ ├── registration │ │ └── login.html │ └── sshkey │ │ ├── form.html │ │ └── home.html ├── test │ ├── __init__.py │ └── gitolite_test.py ├── tools │ ├── __init__.py │ ├── access.py │ ├── archiveRepo.py │ ├── serve.py │ ├── ssh.py │ └── test.py ├── update_messages ├── urls.py └── wsgi.py ├── manage.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .zcache 2 | .tmp* 3 | *.sql 4 | *.pot 5 | 6 | .DS_Store 7 | *.pyc 8 | *.pyo 9 | env 10 | env* 11 | dist 12 | *.egg 13 | *.egg-info 14 | _mailinglist 15 | .tox 16 | 17 | fabfile.py 18 | 19 | localsettings.py 20 | gitweb.conf 21 | 22 | gitube/runfcgi 23 | 24 | /gitube.egg-info/ 25 | 26 | .ropeproject 27 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Gitube is a simple git repository management web application 2 | can be deployed on your own server. 3 | 4 | Im start working on migrate to gitolite. 5 | If you want help, see the develop branch. 6 | And I really need help, send pull requests, please :) 7 | 8 | The develop branch is under early development and not usable yet. 9 | 10 | The master branch is the old verion of gitube, which using gitosis. 11 | 12 | Sponsored by http://bigecko.com 13 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Install gitolite 5 | ---------------- 6 | 7 | **Create user for git**:: 8 | 9 | sudo adduser \ 10 | --system \ 11 | --shell /bin/sh \ 12 | --gecos 'git version control' \ 13 | --group \ 14 | --disabled-password \ 15 | --home /home/git \ 16 | git 17 | 18 | # change user to git 19 | sudo su git 20 | 21 | # cd to git home 22 | cd 23 | 24 | # create bin dir 25 | mkdir bin 26 | 27 | # set PATH for user git 28 | echo "PATH=\$PATH:/home/git/bin\nexport PATH" > /home/git/.profile 29 | export PATH=/home/git/bin:$PATH 30 | 31 | **Clone gitolite and install it**:: 32 | 33 | # get the software 34 | git clone git://github.com/sitaramc/gitolite.git 35 | 36 | # install it 37 | gitolite/install -ln 38 | 39 | # setup the initial repos with your key 40 | gitolite setup -pk your-name.pub 41 | 42 | Now you can `Ctrl + d` to exit git user. 43 | -------------------------------------------------------------------------------- /gitube/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/__init__.py -------------------------------------------------------------------------------- /gitube/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/apps/__init__.py -------------------------------------------------------------------------------- /gitube/apps/account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/apps/account/__init__.py -------------------------------------------------------------------------------- /gitube/apps/account/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | urlpatterns = patterns('gitube.apps.account.views', 4 | url(r'^$', 'index', name='account_home'), 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /gitube/apps/account/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render_to_response 3 | from django.template import RequestContext 4 | 5 | import django_authopenid 6 | from django_authopenid.views import not_authenticated 7 | 8 | 9 | @login_required 10 | def index(request): 11 | """docstring for index""" 12 | return render_to_response('account/home.html', 13 | RequestContext(request, {})) 14 | 15 | @not_authenticated 16 | def register(request): 17 | result = django_authopenid.views.register(request, send_email=False) 18 | user = request.user 19 | 20 | # 'email' in request.POST.keys() indicate is creating a new account. 21 | if (request.POST and 'email' in request.POST.keys() 22 | and user is not None 23 | and not user.is_superuser 24 | and not user.is_anonymous()): 25 | # We need active user by admin. 26 | user.is_active = False 27 | user.save() 28 | return result 29 | -------------------------------------------------------------------------------- /gitube/apps/gitweb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/apps/gitweb/__init__.py -------------------------------------------------------------------------------- /gitube/apps/gitweb/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | urlpatterns = patterns('gitube.apps.gitweb.views', 4 | url(r'^$', 'gitweb', name='gitweb'), 5 | ) 6 | 7 | 8 | -------------------------------------------------------------------------------- /gitube/apps/gitweb/views.py: -------------------------------------------------------------------------------- 1 | import os, subprocess, httplib 2 | from cStringIO import StringIO 3 | 4 | from django.contrib.auth.decorators import login_required 5 | from django.http import HttpResponse 6 | from django.conf import settings 7 | 8 | @login_required 9 | def gitweb(request): 10 | cgienv = { 11 | 'REQUEST_METHOD': request.method, 12 | 'QUERY_STRING': request.META['QUERY_STRING'], 13 | 'GITWEB_SITENAME': 'gitub', 14 | 'GITWEB_CONFIG': getattr(settings, 'PROJECT_PATH') + '/gitweb.conf', 15 | } 16 | os.environ.update(cgienv) 17 | 18 | p = subprocess.Popen([getattr(settings, 'PROJECT_PATH') + '/gitweb.cgi'], 19 | stdin = subprocess.PIPE, 20 | stdout = subprocess.PIPE, 21 | stderr = subprocess.PIPE 22 | ) 23 | 24 | stdout, stderr = p.communicate() 25 | 26 | res = httplib.HTTPResponse(FakeSock(), method='GET') 27 | res.fp = StringIO(stdout) 28 | res.begin() 29 | 30 | if not res.getheaders(): 31 | msg = httplib.HTTPMessage(StringIO(stdout)) 32 | else: 33 | msg = res.msg 34 | 35 | #remove headers 36 | while True: 37 | line = res.fp.readline() 38 | if line == '\r\n': break 39 | 40 | response = HttpResponse(res.read(), mimetype=msg.getheader('content-type')) 41 | for item in msg.items(): 42 | response[item[0]] = item[1] 43 | return response 44 | 45 | class FakeSock(object): 46 | def makefile(self, mode, bufsize): 47 | pass 48 | 49 | -------------------------------------------------------------------------------- /gitube/apps/project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/apps/project/__init__.py -------------------------------------------------------------------------------- /gitube/apps/project/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from gitube.apps.project import models 3 | 4 | admin.site.register(models.Project) 5 | admin.site.register(models.Repository) 6 | admin.site.register(models.ProjectUserRoles) 7 | -------------------------------------------------------------------------------- /gitube/apps/project/fixtures/testing_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"model":"auth.User", "pk":1, "fields":{"username":"harryxu"}}, 3 | {"model":"auth.User", "pk":2, "fields":{"username":"sarah"}}, 4 | {"model":"auth.User", "pk":3, "fields":{"username":"chloe"}}, 5 | {"model":"auth.User", "pk":4, "fields":{"username":"jack"}}, 6 | {"model":"auth.User", "pk":5, "fields":{"username":"kim"}}, 7 | {"model":"auth.User", "pk":6, "fields":{"username":"clark"}}, 8 | 9 | {"model":"auth.Group", "pk":1, "fields":{"name":"admin"}}, 10 | {"model":"auth.Group", "pk":2, "fields":{"name":"developer"}}, 11 | {"model":"auth.Group", "pk":3, "fields":{"name":"guest"}}, 12 | 13 | {"model":"project.Project", "pk":1, "fields":{"name":"mars", "owner":1, "slug":"mars"}}, 14 | 15 | { 16 | "model":"project.Repository", 17 | "pk":1, 18 | "fields":{"name":"flex", "project":1, "path_hash":"1"} 19 | }, 20 | 21 | { 22 | "model":"project.Repository", 23 | "pk":2, 24 | "fields":{"name":"cosmos", "project":1, "path_hash":"2"} 25 | } 26 | 27 | ] 28 | -------------------------------------------------------------------------------- /gitube/apps/project/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from gitube.forms import BaseModelForm 4 | from gitube.apps.project import models 5 | 6 | 7 | class ProjectFrom(BaseModelForm): 8 | class Meta: 9 | model = models.Project 10 | fields = ('name', 'slug', 'description', 'is_public') 11 | 12 | class RepositoryForm(BaseModelForm): 13 | class Meta: 14 | model = models.Repository 15 | fields = ('name', 'slug', 'description') 16 | 17 | class ProjectMemberForm(BaseModelForm): 18 | class Meta: 19 | model = models.ProjectUserRoles 20 | widgets = { 21 | 'project': forms.HiddenInput(), 22 | } 23 | -------------------------------------------------------------------------------- /gitube/apps/project/models.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from django.db import models 4 | from django.db.models import Q 5 | from django.contrib.auth.models import User, Group 6 | 7 | from django.conf import settings 8 | tblname = getattr(settings, 'TABLE_NAME_FORMAT', 'gitube_%s') 9 | 10 | class Project(models.Model): 11 | name = models.CharField(max_length=50, unique=True) 12 | description = models.TextField() 13 | owner = models.ForeignKey(User) 14 | create_at = models.DateTimeField(auto_now_add=True) 15 | update_at = models.DateTimeField(auto_now=True) 16 | slug = models.SlugField(max_length=255, unique=True) 17 | is_public = models.BooleanField(default=0) 18 | 19 | class Meta: 20 | db_table = tblname % 'projects' 21 | ordering = ["name"] 22 | 23 | def __unicode__(self): 24 | return self.name 25 | 26 | def canRead(self, user): 27 | if user == self.owner or self.is_public: 28 | return True 29 | try: 30 | ProjectUserRoles.objects.get(project=self, user=user) 31 | return True 32 | except ProjectUserRoles.DoesNotExist: 33 | return False 34 | 35 | def canPush(self, user): 36 | """Check if user can push code to project repos.""" 37 | if user == self.owner or self.is_public: 38 | return True 39 | try: 40 | ProjectUserRoles.objects.get( 41 | Q(group=Group.objects.get(name='admin'))|Q(group=Group.objects.get(name='developer')), 42 | project=self, user=user) 43 | return True 44 | except ProjectUserRoles.DoesNotExist: 45 | return False 46 | 47 | def isAdmin(self, user): 48 | if user == self.owner: 49 | return True 50 | try: 51 | ProjectUserRoles.objects.get( 52 | project=self, user=user, group=Group.objects.get(name='admin')) 53 | return True 54 | except ProjectUserRoles.DoesNotExist: 55 | return False 56 | 57 | @models.permalink 58 | def get_absolute_url(self): 59 | return ('view_project', (), { 60 | 'pslug': str(self.slug)}) 61 | 62 | class Repository(models.Model): 63 | name = models.CharField(max_length=50) 64 | description = models.TextField() 65 | project = models.ForeignKey(Project) 66 | create_at = models.DateTimeField(auto_now_add=True) 67 | update_at = models.DateTimeField(auto_now=True) 68 | slug = models.SlugField(max_length=255) 69 | path_hash = models.CharField(max_length=64, unique=True) 70 | is_public = models.BooleanField(default=0) 71 | 72 | class Meta: 73 | db_table = tblname % 'repositories' 74 | ordering = ['name'] 75 | 76 | def save(self, *args, **kwargs): 77 | """docstring for save""" 78 | path = '%s/%s' % (self.project.slug, self.slug) 79 | self.path_hash = hashlib.sha1(path).hexdigest() 80 | super(Repository, self).save(*args, **kwargs) 81 | 82 | def canRead(self, user): 83 | if user == self.project.owner or self.is_public: 84 | return True 85 | try: 86 | RepositoryUserRoles.objects.get(repo=self, user=user) 87 | return True 88 | except RepositoryUserRoles.DoesNotExist: 89 | return self.project.canRead(user) 90 | 91 | def canPush(self, user): 92 | """docstring for canPush""" 93 | if user == self.project.owner or self.is_public: 94 | return True 95 | return self.project.canPush(user) 96 | 97 | def isAdmin(self, user): 98 | if user == self.project.owner: 99 | return True 100 | try: 101 | RepositoryUserRoles.objects.get( 102 | repo=self, user=user, group=Group.objects.get(name='admin')) 103 | return True 104 | except RepositoryUserRoles.DoesNotExist: 105 | return self.project.isAdmin(user) 106 | 107 | def __unicode__(self): 108 | return self.name 109 | 110 | def gitAddres(self): 111 | """docstring for gitAddres""" 112 | addr = '%(user)s@%(host)s:%(project)s/%(repo)s.git' % { 113 | 'user': getattr(settings, 'SSH_USER'), 114 | 'host': getattr(settings, 'GIT_HOST'), 115 | 'project':self.project.slug, 116 | 'repo':self.slug} 117 | return addr 118 | 119 | def gitwebPath(self): 120 | path = '%s/%s.git' %(self.project.slug, self.slug) 121 | return path 122 | 123 | @models.permalink 124 | def get_absolute_url(self): 125 | return ('view_repo', (), { 126 | 'pslug': str(self.project.slug), 127 | 'rslug': str(self.slug)}) 128 | 129 | 130 | class RepositoryUserRoles(models.Model): 131 | user = models.ForeignKey(User) 132 | group = models.ForeignKey(Group) 133 | repo = models.ForeignKey(Repository) 134 | 135 | class Meta: 136 | db_table = tblname % 'repository_user_roles' 137 | 138 | class ProjectUserRoles(models.Model): 139 | user = models.ForeignKey(User) 140 | group = models.ForeignKey(Group) 141 | project = models.ForeignKey(Project) 142 | 143 | class Meta: 144 | db_table = tblname % 'project_user_roles' 145 | unique_together = ('user', 'project') 146 | -------------------------------------------------------------------------------- /gitube/apps/project/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth.models import User, Group 3 | 4 | from gitube.apps.project.models import * 5 | 6 | class RepositoryTestCase(TestCase): 7 | fixtures = ['testing_data.json'] 8 | 9 | def setUp(self): 10 | users = User.objects.order_by('id').all() 11 | self.harryxu = users[0] 12 | self.sarah = users[1] 13 | self.chloe = users[2] 14 | self.jack = users[3] 15 | self.kim = users[4] 16 | self.clark = users[5] 17 | 18 | groups = Group.objects.order_by('id').all() 19 | self.admin = groups[0] 20 | self.developer = groups[1] 21 | self.guest = groups[2] 22 | 23 | self.marsProj = Project.objects.get(owner=self.harryxu) 24 | 25 | repos = Repository.objects.order_by('id').filter(project=self.marsProj) 26 | self.flexRepo = repos[0] 27 | self.cosmosRepo = repos[1] 28 | 29 | def testCanRead_repo_user(self): 30 | """Test user can/not view repo.""" 31 | # add chloe to flex repo as a developer 32 | r = RepositoryUserRoles.objects.create( 33 | user = self.chloe, 34 | group = self.developer, 35 | repo = self.flexRepo) 36 | 37 | self.assertTrue(self.flexRepo.canRead(self.harryxu)) 38 | self.assertTrue(self.flexRepo.canRead(self.chloe)) 39 | # Neither sarah is owner nor a user in flex repo. 40 | self.assertFalse(self.flexRepo.canRead(self.sarah)) 41 | 42 | r.delete() 43 | 44 | def testIsAdmin_repo_user(self): 45 | """Test user can/not edit repo.""" 46 | r1 = RepositoryUserRoles.objects.create( 47 | user = self.chloe, group = self.admin, repo = self.flexRepo) 48 | r2 = RepositoryUserRoles.objects.create( 49 | user = self.sarah, group = self.developer, repo = self.flexRepo) 50 | 51 | self.assertTrue(self.flexRepo.isAdmin(self.harryxu)) 52 | self.assertTrue(self.flexRepo.isAdmin(self.chloe)) 53 | self.assertFalse(self.flexRepo.isAdmin(self.sarah)) 54 | self.assertFalse(self.flexRepo.isAdmin(self.clark)) 55 | 56 | r1.delete() 57 | r2.delete() 58 | 59 | def testCanRead_project_user(self): 60 | """docstring for testCanRead_project_user""" 61 | r1 = ProjectUserRoles.objects.create( 62 | user=self.kim, group=self.guest, project=self.marsProj) 63 | r2 = ProjectUserRoles.objects.create( 64 | user=self.jack, group=self.developer, project=self.marsProj) 65 | 66 | self.assertTrue(self.marsProj.canRead(self.kim)) 67 | self.assertTrue(self.marsProj.canRead(self.jack)) 68 | self.assertTrue(self.flexRepo.canRead(self.kim)) 69 | self.assertTrue(self.flexRepo.canRead(self.jack)) 70 | 71 | self.assertFalse(self.marsProj.canRead(self.chloe)) 72 | self.assertFalse(self.flexRepo.canRead(self.chloe)) 73 | 74 | r1.delete() 75 | r2.delete() 76 | 77 | ######## Test can push ####### 78 | def testCanPush_nobody(self): 79 | self.assertFalse(self.marsProj.canPush(self.clark)) 80 | self.assertFalse(self.flexRepo.canPush(self.clark)) 81 | 82 | def testCanPush_guest(self): 83 | guest_kim = ProjectUserRoles.objects.create( 84 | user=self.kim, group=self.guest, project=self.marsProj) 85 | self.assertFalse(self.marsProj.canPush(self.kim)) 86 | self.assertFalse(self.flexRepo.canPush(self.kim)) 87 | guest_kim.delete() 88 | 89 | def testCanPush_owner(self): 90 | self.assertTrue(self.marsProj.canPush(self.harryxu)) 91 | self.assertTrue(self.flexRepo.canPush(self.harryxu)) 92 | 93 | def testCanPush_developer(self): 94 | developer_jack = ProjectUserRoles.objects.create( 95 | user=self.jack, group=self.developer, project=self.marsProj) 96 | self.assertTrue(self.marsProj.canPush(self.jack)) 97 | self.assertTrue(self.flexRepo.canPush(self.jack)) 98 | developer_jack.delete() 99 | 100 | def testCanPush_admin(self): 101 | admin_sarah = ProjectUserRoles.objects.create( 102 | user=self.sarah, group=self.admin, project=self.marsProj) 103 | self.assertTrue(self.marsProj.canPush(self.sarah)) 104 | self.assertTrue(self.flexRepo.canPush(self.sarah)) 105 | admin_sarah.delete() 106 | 107 | def testIsAdmin_project_user(self): 108 | """docstring for testIsAdmin_project_user""" 109 | r1 = ProjectUserRoles.objects.create( 110 | user=self.kim, group=self.guest, project=self.marsProj) 111 | r2 = ProjectUserRoles.objects.create( 112 | user=self.jack, group=self.developer, project=self.marsProj) 113 | r3 = ProjectUserRoles.objects.create( 114 | user=self.chloe, group=self.admin, project=self.marsProj) 115 | 116 | self.assertTrue(self.marsProj.isAdmin(self.harryxu)) 117 | self.assertTrue(self.marsProj.isAdmin(self.chloe)) 118 | 119 | self.assertFalse(self.marsProj.isAdmin(self.kim)) 120 | self.assertFalse(self.marsProj.isAdmin(self.jack)) 121 | 122 | r1.delete() 123 | r2.delete() 124 | 125 | -------------------------------------------------------------------------------- /gitube/apps/project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | urlpatterns = patterns('gitube.apps.project.views', 4 | # project level 5 | url(r'^create/$', 'createProject', name='create_project'), 6 | url(r'^(?P[^/]+)/$', 'viewProject', name='view_project'), 7 | url(r'^(?P[^/]+)/edit/$', 'editProject', name='edit_project'), 8 | 9 | # project members 10 | url(r'^(?P[^/]+)/members/$', 'listProjectMembers', name='list_project_members'), 11 | url(r'^(?P[^/]+)/members/add/$', 'addProjectMember', name='add_project_member'), 12 | url(r'^(?P[^/]+)/members/del/(?P\d+)/$', 13 | 'removeProjectMember', name='del_project_member'), 14 | 15 | # repo level 16 | url(r'^(?P\S+)/newrepo/$', 'createRepository', name='create_repo'), 17 | url(r'^(?P[^/]+)/r/(?P[^/]+)/$', 'viewRepository', name='view_repo'), 18 | url(r'^(?P[^/]+)/r/(?P[^/]+)/edit/$', 'editRepository', name='edit_repo'), 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /gitube/apps/project/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404, render_to_response, redirect 2 | from django.http import Http404 3 | from django.contrib.auth.decorators import login_required 4 | from django.template import RequestContext 5 | from django.contrib.auth.models import User 6 | 7 | from gitube.apps.project import models, forms 8 | 9 | def home(request): 10 | """docstring for start""" 11 | viewData = {} 12 | if request.user.is_authenticated(): 13 | viewData['projects'] = models.Project.objects.filter(owner=request.user) 14 | viewData['projectRoles'] = models.ProjectUserRoles.objects.filter(user=request.user) 15 | return render_to_response('home.html', 16 | RequestContext(request, viewData)) 17 | 18 | 19 | ######################### Project ####################### 20 | 21 | def viewProject(request, pslug): 22 | """docstring for viewProject""" 23 | project = get_object_or_404(models.Project, slug=pslug) 24 | if not project.canRead(request.user): 25 | raise Http404 26 | repos = project.repository_set.all() 27 | 28 | return render_to_response('project/view_project.html', RequestContext(request, { 29 | 'project':project, 30 | 'repositories':repos 31 | })) 32 | 33 | @login_required 34 | def createProject(request): 35 | """Create a new project""" 36 | if request.method == 'GET': 37 | form = forms.ProjectFrom() 38 | else: 39 | project = models.Project() 40 | project.owner = request.user 41 | form = forms.ProjectFrom(request.POST, instance=project) 42 | if form.is_valid(): 43 | form.save() 44 | return redirect(project) 45 | 46 | return render_to_response('project/project_form.html', RequestContext(request, { 47 | 'form': form, 48 | })) 49 | 50 | @login_required 51 | def editProject(request, pslug): 52 | project = get_object_or_404(models.Project, slug=pslug) 53 | if not project.isAdmin(request.user): 54 | raise Http404 55 | if request.method == 'POST': 56 | form = forms.ProjectFrom(request.POST, instance=project) 57 | if form.is_valid(): 58 | form.save() 59 | return redirect(project) 60 | else: 61 | form = forms.ProjectFrom(instance=project) 62 | 63 | return render_to_response('project/project_form.html', RequestContext(request, { 64 | 'project': project, 65 | 'form': form, 66 | })) 67 | 68 | ######################### Project members ####################### 69 | @login_required 70 | def listProjectMembers(request, pslug): 71 | project = get_object_or_404(models.Project, slug=pslug) 72 | if not project.canRead(request.user): 73 | raise Http404 74 | 75 | purs = models.ProjectUserRoles.objects.filter(project=project) 76 | return render_to_response('project/members.html', 77 | RequestContext(request, {'project':project, 'purs':purs})) 78 | 79 | @login_required 80 | def addProjectMember(request, pslug): 81 | """docstring for addProjectMember""" 82 | project = get_object_or_404(models.Project, slug=pslug) 83 | if not project.isAdmin(request.user): 84 | raise Http404 85 | 86 | if request.method == 'POST': 87 | form = forms.ProjectMemberForm(request.POST) 88 | if form.is_valid(): 89 | form.save() 90 | return redirect('list_project_members', pslug=project.slug) 91 | else: 92 | form = forms.ProjectMemberForm(initial={'project':project.id}) 93 | 94 | return render_to_response('project/member_add_form.html', 95 | RequestContext(request, {'form':form, 'project':project})) 96 | 97 | @login_required 98 | def removeProjectMember(request, pslug, userId): 99 | """docstring for remove""" 100 | project = get_object_or_404(models.Project, slug=pslug) 101 | if not project.isAdmin(request.user): 102 | raise Http404 103 | user = get_object_or_404(User, pk=userId) 104 | purs = get_object_or_404(models.ProjectUserRoles, project=project, user=user) 105 | purs.delete() 106 | return redirect('list_project_members', pslug=project.slug) 107 | 108 | ######################### Repository ####################### 109 | 110 | @login_required 111 | def viewRepository(request, pslug, rslug): 112 | """docstring for viewRepository""" 113 | project = get_object_or_404(models.Project , slug=pslug) 114 | repo = get_object_or_404(models.Repository, slug=rslug, project=project) 115 | return render_to_response('project/view_repository.html', 116 | RequestContext(request, {'repository': repo})) 117 | 118 | @login_required 119 | def createRepository(request, pslug): 120 | """docstring for createRepo""" 121 | project = get_object_or_404(models.Project, slug=pslug) 122 | if not project.isAdmin(request.user): 123 | raise Http404 124 | if request.method == 'GET': 125 | form = forms.RepositoryForm() 126 | else: 127 | repo = models.Repository() 128 | repo.project = project 129 | form = forms.RepositoryForm(request.POST, instance=repo) 130 | if form.is_valid(): 131 | form.save() 132 | return redirect(repo) 133 | 134 | return render_to_response('project/repository_form.html', RequestContext(request, { 135 | 'form': form, 136 | 'project': project, 137 | })) 138 | 139 | @login_required 140 | def editRepository(request, pslug, rslug): 141 | project = get_object_or_404(models.Project, slug=pslug) 142 | repo = get_object_or_404(models.Repository, slug=rslug, project=project) 143 | if not repo.isAdmin(request.user): 144 | raise Http404 145 | if request.method == 'POST': 146 | form = forms.RepositoryForm(request.POST, instance=repo) 147 | if form.is_valid(): 148 | form.save() 149 | return redirect(repo) 150 | else: 151 | form = forms.RepositoryForm(instance=repo) 152 | 153 | return render_to_response('project/repository_form.html', RequestContext(request, { 154 | 'form': form, 155 | 'project': project, 156 | 'repo' :repo, 157 | })) 158 | 159 | -------------------------------------------------------------------------------- /gitube/apps/sshkey/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/apps/sshkey/__init__.py -------------------------------------------------------------------------------- /gitube/apps/sshkey/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.conf.urls.defaults import patterns 3 | from django.utils.translation import ugettext as _ 4 | from django.http import HttpResponseRedirect 5 | from django.core.exceptions import PermissionDenied 6 | 7 | from gitube.apps.sshkey import models 8 | 9 | class SSHKeyAdmin(admin.ModelAdmin): 10 | def get_urls(self): 11 | urls = super(SSHKeyAdmin, self).get_urls() 12 | my_urls = patterns('', 13 | ('^rebuild_authorized_keys/$', self.rebuildAuthorizedKeys), 14 | ) 15 | return my_urls + urls 16 | 17 | def rebuildAuthorizedKeys(self, request): 18 | if not request.user.has_perm('sshkey.can_rebuild'): 19 | raise PermissionDenied 20 | 21 | self.message_user(request, _('authorized_keys rebuild successed!')) 22 | models.rebuildAuthorizedKeys() 23 | return HttpResponseRedirect('/admin/sshkey/sshkey/') 24 | 25 | admin.site.register(models.SSHKey, SSHKeyAdmin) 26 | -------------------------------------------------------------------------------- /gitube/apps/sshkey/forms.py: -------------------------------------------------------------------------------- 1 | from gitube.forms import BaseModelForm 2 | from gitube.apps.sshkey import models 3 | 4 | class KeyForm(BaseModelForm): 5 | class Meta: 6 | model = models.SSHKey 7 | fields = ('title', 'key') 8 | 9 | -------------------------------------------------------------------------------- /gitube/apps/sshkey/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.db import models 3 | from django.db.models.signals import pre_save, post_save, post_delete 4 | from django.contrib.auth.models import User 5 | from django.conf import settings 6 | 7 | from gitube.tools import ssh 8 | 9 | 10 | tblname = getattr(settings, 'TABLE_NAME_FORMAT', 'gitube_%s') 11 | 12 | 13 | class SSHKey(models.Model): 14 | title = models.CharField(max_length=50) 15 | key = models.TextField() 16 | user = models.ForeignKey(User) 17 | 18 | class Meta: 19 | db_table = tblname % 'ssh_keys' 20 | ordering = ['title'] 21 | 22 | def __unicode__(self): 23 | return self.title 24 | 25 | 26 | 27 | authorized_keys = '~%s/.ssh/authorized_keys' % getattr(settings, 'SSH_USER') 28 | authorized_keys = os.path.expanduser(authorized_keys) 29 | 30 | keysTobeRemove = [] 31 | 32 | def preSaveHandler(sender, instance, **kwargs): 33 | """docstring for preSaveHandler""" 34 | if instance.id is not None and instance.id > 0: 35 | oldKey = sender.objects.filter(pk=instance.pk).get().key 36 | keysTobeRemove.append(oldKey) 37 | 38 | def pubkeySaveHandler(sender, instance, **kwargs): 39 | """docstring for pubkeySaveHandler""" 40 | username = instance.user.username 41 | newKey = instance.key 42 | ssh.writeKey(authorized_keys, username, newKey) 43 | 44 | for key in keysTobeRemove: 45 | ssh.removeKey(authorized_keys, username, key) 46 | keysTobeRemove.remove(key) 47 | 48 | def pubkeyRemoveHandler(sender, instance, **kwargs): 49 | """docstring for pubkeyRemoveHandler""" 50 | if instance.id is not None and instance.id > 0: 51 | ssh.removeKey(authorized_keys, instance.user.username, instance.key) 52 | 53 | def rebuildAuthorizedKeys(): 54 | ssh.rebuildAuthorizedKeys(SSHKey.objects.all(), authorized_keys) 55 | 56 | pre_save.connect(preSaveHandler, sender=SSHKey) 57 | post_save.connect(pubkeySaveHandler, sender=SSHKey) 58 | post_delete.connect(pubkeyRemoveHandler, sender=SSHKey) 59 | -------------------------------------------------------------------------------- /gitube/apps/sshkey/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | -------------------------------------------------------------------------------- /gitube/apps/sshkey/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | urlpatterns = patterns('gitube.apps.sshkey.views', 4 | url(r'^$', 'index', name='public_keys_home'), 5 | url(r'^create/$', 'create', name='create_public_key'), 6 | url(r'^(?P\d+)/edit/$', 'edit', name='edit_public_key'), 7 | url(r'^(?P\d+)/del/$', 'delete', name='del_public_key'), 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /gitube/apps/sshkey/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404, render_to_response, redirect 2 | from django.contrib.auth.decorators import login_required 3 | from django.template import RequestContext 4 | 5 | from gitube.apps.sshkey import models, forms 6 | 7 | @login_required 8 | def index(request): 9 | """docstring for index""" 10 | keys = models.SSHKey.objects.filter(user=request.user) 11 | return render_to_response('sshkey/home.html', 12 | RequestContext(request, {'keys':keys})) 13 | 14 | @login_required 15 | def create(request): 16 | """docstring for create""" 17 | if request.method == 'POST': 18 | key = models.SSHKey() 19 | key.user = request.user 20 | form = forms.KeyForm(request.POST, instance=key) 21 | if form.is_valid(): 22 | form.save() 23 | return redirect('public_keys_home') 24 | else: 25 | form = forms.KeyForm() 26 | return render_to_response('sshkey/form.html', 27 | RequestContext(request, { 28 | 'form': form, 29 | 'action': 'Create'})) 30 | 31 | @login_required 32 | def edit(request, id): 33 | """docstring for edit""" 34 | key = get_object_or_404(models.SSHKey, pk=id, user=request.user) 35 | if request.method == 'POST': 36 | form = forms.KeyForm(request.POST, instance=key) 37 | if form.is_valid(): 38 | form.save() 39 | return redirect('public_keys_home') 40 | else: 41 | form = forms.KeyForm(instance=key) 42 | return render_to_response('sshkey/form.html', 43 | RequestContext(request, { 44 | 'form': form, 45 | 'action': 'Edit'})) 46 | 47 | @login_required 48 | def delete(request, id): 49 | """docstring for delete""" 50 | key = get_object_or_404(models.SSHKey, pk=id, user=request.user) 51 | key.delete() 52 | return redirect('public_keys_home') 53 | -------------------------------------------------------------------------------- /gitube/fixture.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"model":"auth.Group", "pk":1, "fields":{"name":"admin"}}, 3 | {"model":"auth.Group", "pk":2, "fields":{"name":"developer"}}, 4 | {"model":"auth.Group", "pk":3, "fields":{"name":"guest"}} 5 | ] 6 | -------------------------------------------------------------------------------- /gitube/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | class BaseModelForm(ModelForm): 4 | def as_table(self): 5 | "Returns this form rendered as HTML s -- excluding the
." 6 | return self._html_output( 7 | normal_row = u'%(label)s%(field)s%(help_text)s%(errors)s', 8 | error_row = u'%s', 9 | row_ender = u'', 10 | help_text_html = u'
%s', 11 | errors_on_separate_row = False) 12 | 13 | -------------------------------------------------------------------------------- /gitube/gitweb.conf.example: -------------------------------------------------------------------------------- 1 | # path to git projects (.git) 2 | $projectroot = "/home/git/repositories"; 3 | 4 | # directory to use for temp files 5 | $git_temp = "/tmp"; 6 | 7 | # target of the home link on top of all pages 8 | #$home_link = $my_uri || "/"; 9 | 10 | # html text to include at home page 11 | $home_text = "indextext.html"; 12 | 13 | # file with project list; by default, simply scan the projectroot dir. 14 | $projects_list = $projectroot; 15 | 16 | # stylesheet to use 17 | $stylesheet = "/static/gitweb.css"; 18 | 19 | # logo to use 20 | $logo = "/static/git-logo.png"; 21 | 22 | # the 'favicon' 23 | $favicon = "/static/favicon.ico"; 24 | -------------------------------------------------------------------------------- /gitube/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/lib/__init__.py -------------------------------------------------------------------------------- /gitube/lib/gitolite.py: -------------------------------------------------------------------------------- 1 | """ 2 | gitolite conf file and ssh key managent. 3 | Inspired by gitolite: 4 | A Ruby interface for the gitolite git backend system. 5 | https://github.com/wingrunr21/gitolite 6 | """ 7 | 8 | import re 9 | 10 | class Conf(object): 11 | """gitolite conf file management """ 12 | 13 | def __init__(self, filename='gitolite.conf'): 14 | self.filename = filename 15 | 16 | self.groups = {} 17 | self.group_names = [] # keep group definition order 18 | 19 | self.repos = {} 20 | 21 | def add_group(self, group): 22 | self.groups[group.name] = group 23 | self.group_names.append(group.name) 24 | 25 | def del_group(self, group_name): 26 | if self.has_group(group_name): 27 | del self.groups[group_name] 28 | self.group_names.remove(group_name) 29 | 30 | def has_group(self, group_name): 31 | return group_name in self.groups 32 | 33 | def get_group(self, group_name): 34 | if not self.has_group(group_name): 35 | return None 36 | return self.groups[group_name] 37 | 38 | def add_repo(self, repo): 39 | self.repos[repo.name] = repo 40 | 41 | def del_repo(self, repo_name): 42 | del self.repos[repo_name] 43 | 44 | def has_repo(self, repo_name): 45 | return repo_name in self.repos 46 | 47 | def get_repo(self, repo_name): 48 | if not self.has_repo(repo_name): 49 | return None 50 | return self.repos[repo_name] 51 | 52 | def parse_conf(self, conf_str): 53 | lines = conf_str.splitlines(True) 54 | for line in lines: 55 | line = line.strip() 56 | 57 | # match repo definition 58 | if self._rematch('^repo\s+(.+)', line): 59 | repo_names = self._matches.group(1).strip().split(' ') 60 | for reponame in repo_names: 61 | reponame = reponame.strip() 62 | if reponame: 63 | self.add_repo(Repo(reponame)) 64 | 65 | # match permission definition in repo 66 | elif self._rematch('^(-|R|W|RW\+?)\s+(\S+)?\s*=\s+(.+)', line): 67 | perm = self._matches.group(1) 68 | refex = self._matches.group(2) 69 | if not refex: 70 | refex = '' 71 | users = self._matches.group(3).split(' ') 72 | for reponame in repo_names: 73 | self.get_repo(reponame).add_permission(perm, refex, *users) 74 | 75 | # match group definition 76 | elif self._rematch('^@(\S+)\s+=\s+(.+)', line): 77 | group_name = self._matches.group(1) 78 | users = self._matches.group(2).split(' ') 79 | 80 | if not self.has_group(group_name): 81 | self.add_group(Group(group_name)) 82 | 83 | self.get_group(group_name).add_users(*users) 84 | 85 | def _rematch(self, pattern, string, flags=0): 86 | matches = re.match(pattern, string, flags) 87 | self._matches = matches 88 | return matches 89 | 90 | def __str__(self): 91 | groups = [str(self.groups[name]) for name in self.group_names] 92 | repos = [str(repo) for repo in self.repos.values()] 93 | 94 | return '%s\n\n%s' % ('\n'.join(groups), '\n\n'.join(repos)) 95 | 96 | 97 | class Group(object): 98 | def __init__(self, name): 99 | self.name = name 100 | self.users = [] 101 | 102 | def add_users(self, *users): 103 | for user in users: 104 | if not user in self.users: 105 | self.users.append(user) 106 | 107 | def del_users(self, *users): 108 | for user in users: 109 | if user in self.users: 110 | self.users.remove(user) 111 | 112 | def has_user(self, user): 113 | return user in self.users 114 | 115 | def __str__(self): 116 | return '@%s = %s' % (self.name, ' '.join(self.users)) 117 | 118 | 119 | class Repo(object): 120 | """gitolite repo""" 121 | 122 | def __init__(self, name): 123 | self.name = name 124 | self.permissions = {} 125 | 126 | def add_permission(self, perm, refex='', *users): 127 | for user in users: 128 | key = user 129 | if perm == '-': 130 | key = user + '-' 131 | 132 | self.permissions[key] = '%s %s = %s' % (perm, refex, user) 133 | 134 | def del_permission(self, *users): 135 | for user in users: 136 | if user in self.permissions: 137 | del self.permissions[user] 138 | if user + '-' in self.permissions: 139 | del self.permissions[user + '-'] 140 | 141 | def __str__(self): 142 | users = [perm for perm in self.permissions.values()] 143 | return 'repo %s\n %s' % (self.name, '\n '.join(users)) 144 | -------------------------------------------------------------------------------- /gitube/locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /gitube/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2012-04-08 19:54+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: apps/sshkey/admin.py:21 21 | msgid "authorized_keys rebuild successed!" 22 | msgstr "" 23 | 24 | #: templates/base.html:29 templates/account/home.html:6 25 | msgid "Manage public keys" 26 | msgstr "管理公钥" 27 | 28 | #: templates/base.html:30 templates/account/home.html:7 29 | #, fuzzy 30 | msgid "Change password" 31 | msgstr "修改密码" 32 | 33 | #: templates/base.html:34 34 | msgid "Sign out" 35 | msgstr "退出" 36 | 37 | #: templates/base.html:36 38 | msgid "Sign in" 39 | msgstr "登录" 40 | 41 | #: templates/home.html:9 42 | msgid "My Projects:" 43 | msgstr "我的项目:" 44 | 45 | #: templates/home.html:16 46 | msgid "You have no project yet." 47 | msgstr "您还没有任何项目" 48 | 49 | #: templates/home.html:22 50 | msgid "Projects I have joined." 51 | msgstr "我参与的项目" 52 | 53 | #: templates/home.html:29 54 | #, fuzzy 55 | msgid "You have not joined any project yet." 56 | msgstr "您还没有任何项目:" 57 | 58 | #: templates/home.html:44 59 | msgid "A project can contain one or more git repos" 60 | msgstr "一个项目中可包含多个git仓库" 61 | 62 | #: templates/home.html:46 63 | msgid "Create a new project" 64 | msgstr "创建一个新项目" 65 | 66 | #: templates/admin/sshkey/sshkey/change_list.html:9 67 | #, python-format 68 | msgid "Add %(name)s" 69 | msgstr "" 70 | 71 | #: templates/admin/sshkey/sshkey/change_list.html:13 72 | msgid "Rebuild authorized_keys" 73 | msgstr "" 74 | 75 | #: templates/authopenid/associate.html:19 76 | msgid "Add a new OpenID URL" 77 | msgstr "增加一个新的OpenID地址" 78 | 79 | #: templates/authopenid/associate.html:25 templates/authopenid/signin.html:40 80 | msgid "OpenId URL :" 81 | msgstr "OpenId地址" 82 | 83 | #: templates/authopenid/associate.html:26 84 | msgid "Add OpenID" 85 | msgstr "增加OpenID" 86 | 87 | #: templates/authopenid/complete.html:12 88 | msgid "Your OpenID is verified! " 89 | msgstr "您的OpenID已验证" 90 | 91 | #: templates/authopenid/complete.html:13 92 | msgid "" 93 | "\n" 94 | "\t

Your OpenID can now be associated with a new or existing membership. " 95 | "You can change the association later in your preferences.

\n" 96 | "\t" 97 | msgstr "" 98 | 99 | #: templates/authopenid/complete.html:17 100 | msgid "Associate your OpenID" 101 | msgstr "关联您的OpenID" 102 | 103 | #: templates/authopenid/complete.html:18 104 | msgid "" 105 | "\n" 106 | "\t

If you're joining Sitename, associate your OpenID with " 107 | "a new account. If you're already a member, associate with your existing " 108 | "account.

\n" 109 | "\t" 110 | msgstr "" 111 | 112 | #: templates/authopenid/complete.html:26 templates/authopenid/complete.html:36 113 | #: templates/authopenid/signin.html:11 114 | msgid "Please correct errors below:" 115 | msgstr "请修正下面的错误:" 116 | 117 | #: templates/authopenid/complete.html:50 118 | msgid "A new account" 119 | msgstr "一个新的帐号" 120 | 121 | #: templates/authopenid/complete.html:51 templates/authopenid/complete.html:66 122 | msgid "Username" 123 | msgstr "用户名" 124 | 125 | #: templates/authopenid/complete.html:52 126 | msgid "Email" 127 | msgstr "邮箱" 128 | 129 | #: templates/authopenid/complete.html:53 130 | msgid "Create account" 131 | msgstr "创建帐号" 132 | 133 | #: templates/authopenid/complete.html:65 134 | msgid "An existing account" 135 | msgstr "一个已有的帐号" 136 | 137 | #: templates/authopenid/complete.html:67 138 | msgid "Password" 139 | msgstr "密码" 140 | 141 | #: templates/authopenid/complete.html:68 142 | msgid "Verify" 143 | msgstr "验证" 144 | 145 | #: templates/authopenid/dissociate.html:21 146 | msgid "Dissociate OpenID URL" 147 | msgstr "取消OpenID地址的关联" 148 | 149 | #: templates/authopenid/dissociate.html:23 150 | msgid "Dissociate OpenID" 151 | msgstr "取消OpenID的关联" 152 | 153 | #: templates/authopenid/password_change_form.html:11 154 | msgid "Set password" 155 | msgstr "设置密码" 156 | 157 | #: templates/authopenid/password_change_form.html:12 158 | msgid "" 159 | "Please enter your new password twice so we can verify you typed it in " 160 | "correctly." 161 | msgstr "" 162 | 163 | #: templates/authopenid/password_change_form.html:16 164 | #: templates/authopenid/password_change_form.html:32 165 | msgid "New password:" 166 | msgstr "新密码:" 167 | 168 | #: templates/authopenid/password_change_form.html:18 169 | #: templates/authopenid/password_change_form.html:34 170 | msgid "Confirm password:" 171 | msgstr "确认密码:" 172 | 173 | #: templates/authopenid/password_change_form.html:19 174 | #: templates/authopenid/password_change_form.html:36 175 | msgid "Change my password" 176 | msgstr "修改密码" 177 | 178 | #: templates/authopenid/password_change_form.html:24 179 | msgid "Password change" 180 | msgstr "" 181 | 182 | #: templates/authopenid/password_change_form.html:25 183 | msgid "" 184 | "Please enter your old password, for security's sake, and then enter your new " 185 | "password twice so we can verify you typed it in correctly." 186 | msgstr "请输入您目前所使用的密码" 187 | 188 | #: templates/authopenid/password_change_form.html:30 189 | msgid "Old password:" 190 | msgstr "当前密码" 191 | 192 | #: templates/authopenid/signin.html:34 193 | msgid "Sign In Using Your OpenID URL" 194 | msgstr "使用您的OpenID登录" 195 | 196 | #: templates/authopenid/signin.html:36 197 | msgid "Please click your account provider:" 198 | msgstr "请选择您的OpenID账户" 199 | 200 | #: templates/authopenid/signin.html:41 201 | msgid "Sign in with OpenID" 202 | msgstr "使用OpenID登录" 203 | 204 | #: templates/project/member_add_form.html:8 205 | msgid "Add member" 206 | msgstr "添加成员" 207 | 208 | #: templates/project/member_add_form.html:11 209 | #: templates/project/member_base.html:9 210 | msgid "Add" 211 | msgstr "添加" 212 | 213 | #: templates/project/member_add_form.html:12 214 | msgid "Back" 215 | msgstr "返回" 216 | 217 | #: templates/project/member_base.html:6 templates/project/members.html:5 218 | #: templates/project/view_project.html:10 219 | msgid "Members" 220 | msgstr "成员" 221 | 222 | #: templates/project/members.html:13 templates/sshkey/home.html:14 223 | msgid "Delete" 224 | msgstr "删除" 225 | 226 | #: templates/project/members.html:18 227 | msgid "No members yet." 228 | msgstr "没有成员" 229 | 230 | #: templates/project/project_form.html:11 231 | #, python-format 232 | msgid "Edit project %(name)s" 233 | msgstr "编辑项目 %(name)s" 234 | 235 | #: templates/project/project_form.html:13 236 | #, fuzzy 237 | msgid "Create new project." 238 | msgstr "创建一个新项目" 239 | 240 | #: templates/project/project_form.html:19 241 | #: templates/project/repository_form.html:11 242 | msgid "Save" 243 | msgstr "保存" 244 | 245 | #: templates/project/project_form.html:21 246 | #: templates/project/project_form.html:23 247 | #: templates/project/repository_form.html:13 248 | #: templates/project/repository_form.html:15 templates/sshkey/form.html:12 249 | msgid "Cancel" 250 | msgstr "取消" 251 | 252 | #: templates/project/view_project.html:9 253 | msgid "New repository" 254 | msgstr "创建新仓库" 255 | 256 | #: templates/project/view_project.html:11 257 | #: templates/project/view_repository.html:18 258 | msgid "Admin" 259 | msgstr "管理" 260 | 261 | #: templates/project/view_project.html:15 262 | msgid "Repositories" 263 | msgstr "仓库" 264 | 265 | #: templates/project/view_project.html:26 266 | msgid "No repositories yet." 267 | msgstr "还没有仓库" 268 | 269 | #: templates/project/view_repository.html:13 270 | msgid "git address:" 271 | msgstr "git地址:" 272 | 273 | #: templates/registration/login.html:13 274 | msgid "User login" 275 | msgstr "" 276 | 277 | #: templates/registration/login.html:16 278 | msgid "Login" 279 | msgstr "" 280 | 281 | #: templates/sshkey/home.html:6 282 | msgid "Add new key" 283 | msgstr "添加一个新的公钥" 284 | 285 | #: templates/sshkey/home.html:13 286 | msgid "Edit" 287 | msgstr "编辑" 288 | 289 | #: templates/sshkey/home.html:18 290 | msgid "You have no keys yet." 291 | msgstr "你还没有公钥。" 292 | -------------------------------------------------------------------------------- /gitube/localsettings.py.example: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | TEMPLATE_DEBUG = DEBUG 3 | 4 | ADMINS = ( 5 | # ('Your Name', 'your_email@domain.com'), 6 | ) 7 | 8 | MANAGERS = ADMINS 9 | 10 | DATABASES = { 11 | 'default': { 12 | 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 13 | 'NAME': 'gitube', # Or path to database file if using sqlite3. 14 | 'USER': 'root', # Not used with sqlite3. 15 | 'PASSWORD': '', # Not used with sqlite3. 16 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 17 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 18 | } 19 | } 20 | 21 | TABLE_FORMAT = 'gitube_%s' # Database table name format '%s' is the defined name. 22 | 23 | # Local time zone for this installation. Choices can be found here: 24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 25 | # although not all choices may be available on all operating systems. 26 | # If running in a Windows environment this must be set to the same as your 27 | # system time zone. 28 | TIME_ZONE = 'America/Chicago' 29 | 30 | # Language code for this installation. All choices can be found here: 31 | # http://www.i18nguy.com/unicode/language-identifiers.html 32 | LANGUAGE_CODE = 'en-us' 33 | 34 | # Make this unique, and don't share it with anybody. 35 | SECRET_KEY = '' 36 | 37 | SITE_ID = 1 38 | 39 | # the host for git repos 40 | GIT_HOST = 'localhost' 41 | 42 | # ssh login user 43 | SSH_USER = 'git' 44 | 45 | # The repos base path in the $HOME, all repos will put 46 | # to this path 47 | REPO_BASE_PATH = 'repositories' 48 | -------------------------------------------------------------------------------- /gitube/runfcgi.example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sudo su git -c "./manage.py runfcgi method=prefork \ 3 | socket=/tmp/gitube.sock \ 4 | pidfile=/tmp/gitube.pid \ 5 | maxspare=2 \ 6 | maxchildren=1 \ 7 | umask=73" 8 | -------------------------------------------------------------------------------- /gitube/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Django settings for gitube project. 4 | 5 | PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | # If you set this to False, Django will make some optimizations so as not 8 | # to load the internationalization machinery. 9 | USE_I18N = True 10 | 11 | # Absolute path to the directory that holds media. 12 | # Example: "/home/media/media.lawrence.com/" 13 | MEDIA_ROOT = PROJECT_PATH + '/static/' 14 | 15 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 16 | # trailing slash if there is a path component (optional in other cases). 17 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 18 | MEDIA_URL = '' 19 | 20 | # Absolute path to the directory static files should be collected to. 21 | # Don't put anything in this directory yourself; store your static files 22 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 23 | # Example: "/home/media/media.lawrence.com/static/" 24 | STATIC_ROOT = '' 25 | 26 | # URL prefix for static files. 27 | # Example: "http://media.lawrence.com/static/" 28 | STATIC_URL = '/static/' 29 | 30 | # Additional locations of static files 31 | STATICFILES_DIRS = ( 32 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 33 | # Always use forward slashes, even on Windows. 34 | # Don't forget to use absolute paths, not relative paths. 35 | ) 36 | 37 | # List of finder classes that know how to find static files in 38 | # various locations. 39 | STATICFILES_FINDERS = ( 40 | 'django.contrib.staticfiles.finders.FileSystemFinder', 41 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 42 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 43 | ) 44 | 45 | LOGIN_URL = '/account/signin/' 46 | LOGOUT_URL = '/account/signout/' 47 | LOGIN_REDIRECT_URL = '/account/' 48 | 49 | 50 | # List of callables that know how to import templates from various sources. 51 | TEMPLATE_LOADERS = ( 52 | 'django.template.loaders.filesystem.Loader', 53 | 'django.template.loaders.app_directories.Loader', 54 | ) 55 | 56 | TEMPLATE_CONTEXT_PROCESSORS = ( 57 | 'django.contrib.auth.context_processors.auth', 58 | 'django.core.context_processors.debug', 59 | 'django.core.context_processors.i18n', 60 | 'django.core.context_processors.media', 61 | 'django.core.context_processors.request', 62 | 'django_authopenid.context_processors.authopenid', 63 | ) 64 | 65 | MIDDLEWARE_CLASSES = ( 66 | 'django.middleware.common.CommonMiddleware', 67 | 'django.contrib.sessions.middleware.SessionMiddleware', 68 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 69 | 'django.contrib.messages.middleware.MessageMiddleware', 70 | 71 | # django autopenid middleware 72 | 'django_authopenid.middleware.OpenIDMiddleware', 73 | ) 74 | 75 | ROOT_URLCONF = 'gitube.urls' 76 | 77 | TEMPLATE_DIRS = ( 78 | os.path.join(PROJECT_PATH, 'templates'), 79 | ) 80 | 81 | INSTALLED_APPS = ( 82 | 'django.contrib.auth', 83 | 'django.contrib.contenttypes', 84 | 'django.contrib.sessions', 85 | 'django.contrib.admin', 86 | 'django.contrib.sites', 87 | 'bootstrapform', 88 | 89 | 'django_authopenid', 90 | 'gitube.apps.project', 91 | 'gitube.apps.account', 92 | 'gitube.apps.sshkey', 93 | 'gitube.apps.gitweb', 94 | ) 95 | 96 | 97 | # custom settings 98 | from localsettings import * 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /gitube/static/copier.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/copier.fla -------------------------------------------------------------------------------- /gitube/static/copier.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/copier.swf -------------------------------------------------------------------------------- /gitube/static/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | .clearfix { 11 | *zoom: 1; 12 | } 13 | .clearfix:before, 14 | .clearfix:after { 15 | display: table; 16 | content: ""; 17 | } 18 | .clearfix:after { 19 | clear: both; 20 | } 21 | .hide-text { 22 | overflow: hidden; 23 | text-indent: 100%; 24 | white-space: nowrap; 25 | } 26 | .input-block-level { 27 | display: block; 28 | width: 100%; 29 | min-height: 28px; 30 | /* Make inputs at least the height of their button counterpart */ 31 | 32 | /* Makes inputs behave like true block-level elements */ 33 | 34 | -webkit-box-sizing: border-box; 35 | -moz-box-sizing: border-box; 36 | -ms-box-sizing: border-box; 37 | box-sizing: border-box; 38 | } 39 | .hidden { 40 | display: none; 41 | visibility: hidden; 42 | } 43 | .visible-phone { 44 | display: none; 45 | } 46 | .visible-tablet { 47 | display: none; 48 | } 49 | .visible-desktop { 50 | display: block; 51 | } 52 | .hidden-phone { 53 | display: block; 54 | } 55 | .hidden-tablet { 56 | display: block; 57 | } 58 | .hidden-desktop { 59 | display: none; 60 | } 61 | @media (max-width: 767px) { 62 | .visible-phone { 63 | display: block; 64 | } 65 | .hidden-phone { 66 | display: none; 67 | } 68 | .hidden-desktop { 69 | display: block; 70 | } 71 | .visible-desktop { 72 | display: none; 73 | } 74 | } 75 | @media (min-width: 768px) and (max-width: 979px) { 76 | .visible-tablet { 77 | display: block; 78 | } 79 | .hidden-tablet { 80 | display: none; 81 | } 82 | .hidden-desktop { 83 | display: block; 84 | } 85 | .visible-desktop { 86 | display: none; 87 | } 88 | } 89 | @media (max-width: 480px) { 90 | .nav-collapse { 91 | -webkit-transform: translate3d(0, 0, 0); 92 | } 93 | .page-header h1 small { 94 | display: block; 95 | line-height: 18px; 96 | } 97 | input[type="checkbox"], 98 | input[type="radio"] { 99 | border: 1px solid #ccc; 100 | } 101 | .form-horizontal .control-group > label { 102 | float: none; 103 | width: auto; 104 | padding-top: 0; 105 | text-align: left; 106 | } 107 | .form-horizontal .controls { 108 | margin-left: 0; 109 | } 110 | .form-horizontal .control-list { 111 | padding-top: 0; 112 | } 113 | .form-horizontal .form-actions { 114 | padding-left: 10px; 115 | padding-right: 10px; 116 | } 117 | .modal { 118 | position: absolute; 119 | top: 10px; 120 | left: 10px; 121 | right: 10px; 122 | width: auto; 123 | margin: 0; 124 | } 125 | .modal.fade.in { 126 | top: auto; 127 | } 128 | .modal-header .close { 129 | padding: 10px; 130 | margin: -10px; 131 | } 132 | .carousel-caption { 133 | position: static; 134 | } 135 | } 136 | @media (max-width: 767px) { 137 | body { 138 | padding-left: 20px; 139 | padding-right: 20px; 140 | } 141 | .navbar-fixed-top { 142 | margin-left: -20px; 143 | margin-right: -20px; 144 | } 145 | .container { 146 | width: auto; 147 | } 148 | .row-fluid { 149 | width: 100%; 150 | } 151 | .row { 152 | margin-left: 0; 153 | } 154 | .row > [class*="span"], 155 | .row-fluid > [class*="span"] { 156 | float: none; 157 | display: block; 158 | width: auto; 159 | margin: 0; 160 | } 161 | .thumbnails [class*="span"] { 162 | width: auto; 163 | } 164 | input[class*="span"], 165 | select[class*="span"], 166 | textarea[class*="span"], 167 | .uneditable-input { 168 | display: block; 169 | width: 100%; 170 | min-height: 28px; 171 | /* Make inputs at least the height of their button counterpart */ 172 | 173 | /* Makes inputs behave like true block-level elements */ 174 | 175 | -webkit-box-sizing: border-box; 176 | -moz-box-sizing: border-box; 177 | -ms-box-sizing: border-box; 178 | box-sizing: border-box; 179 | } 180 | .input-prepend input[class*="span"], 181 | .input-append input[class*="span"] { 182 | width: auto; 183 | } 184 | } 185 | @media (min-width: 768px) and (max-width: 979px) { 186 | .row { 187 | margin-left: -20px; 188 | *zoom: 1; 189 | } 190 | .row:before, 191 | .row:after { 192 | display: table; 193 | content: ""; 194 | } 195 | .row:after { 196 | clear: both; 197 | } 198 | [class*="span"] { 199 | float: left; 200 | margin-left: 20px; 201 | } 202 | .container, 203 | .navbar-fixed-top .container, 204 | .navbar-fixed-bottom .container { 205 | width: 724px; 206 | } 207 | .span12 { 208 | width: 724px; 209 | } 210 | .span11 { 211 | width: 662px; 212 | } 213 | .span10 { 214 | width: 600px; 215 | } 216 | .span9 { 217 | width: 538px; 218 | } 219 | .span8 { 220 | width: 476px; 221 | } 222 | .span7 { 223 | width: 414px; 224 | } 225 | .span6 { 226 | width: 352px; 227 | } 228 | .span5 { 229 | width: 290px; 230 | } 231 | .span4 { 232 | width: 228px; 233 | } 234 | .span3 { 235 | width: 166px; 236 | } 237 | .span2 { 238 | width: 104px; 239 | } 240 | .span1 { 241 | width: 42px; 242 | } 243 | .offset12 { 244 | margin-left: 764px; 245 | } 246 | .offset11 { 247 | margin-left: 702px; 248 | } 249 | .offset10 { 250 | margin-left: 640px; 251 | } 252 | .offset9 { 253 | margin-left: 578px; 254 | } 255 | .offset8 { 256 | margin-left: 516px; 257 | } 258 | .offset7 { 259 | margin-left: 454px; 260 | } 261 | .offset6 { 262 | margin-left: 392px; 263 | } 264 | .offset5 { 265 | margin-left: 330px; 266 | } 267 | .offset4 { 268 | margin-left: 268px; 269 | } 270 | .offset3 { 271 | margin-left: 206px; 272 | } 273 | .offset2 { 274 | margin-left: 144px; 275 | } 276 | .offset1 { 277 | margin-left: 82px; 278 | } 279 | .row-fluid { 280 | width: 100%; 281 | *zoom: 1; 282 | } 283 | .row-fluid:before, 284 | .row-fluid:after { 285 | display: table; 286 | content: ""; 287 | } 288 | .row-fluid:after { 289 | clear: both; 290 | } 291 | .row-fluid > [class*="span"] { 292 | float: left; 293 | margin-left: 2.762430939%; 294 | } 295 | .row-fluid > [class*="span"]:first-child { 296 | margin-left: 0; 297 | } 298 | .row-fluid > .span12 { 299 | width: 99.999999993%; 300 | } 301 | .row-fluid > .span11 { 302 | width: 91.436464082%; 303 | } 304 | .row-fluid > .span10 { 305 | width: 82.87292817100001%; 306 | } 307 | .row-fluid > .span9 { 308 | width: 74.30939226%; 309 | } 310 | .row-fluid > .span8 { 311 | width: 65.74585634900001%; 312 | } 313 | .row-fluid > .span7 { 314 | width: 57.182320438000005%; 315 | } 316 | .row-fluid > .span6 { 317 | width: 48.618784527%; 318 | } 319 | .row-fluid > .span5 { 320 | width: 40.055248616%; 321 | } 322 | .row-fluid > .span4 { 323 | width: 31.491712705%; 324 | } 325 | .row-fluid > .span3 { 326 | width: 22.928176794%; 327 | } 328 | .row-fluid > .span2 { 329 | width: 14.364640883%; 330 | } 331 | .row-fluid > .span1 { 332 | width: 5.801104972%; 333 | } 334 | input, 335 | textarea, 336 | .uneditable-input { 337 | margin-left: 0; 338 | } 339 | input.span12, textarea.span12, .uneditable-input.span12 { 340 | width: 714px; 341 | } 342 | input.span11, textarea.span11, .uneditable-input.span11 { 343 | width: 652px; 344 | } 345 | input.span10, textarea.span10, .uneditable-input.span10 { 346 | width: 590px; 347 | } 348 | input.span9, textarea.span9, .uneditable-input.span9 { 349 | width: 528px; 350 | } 351 | input.span8, textarea.span8, .uneditable-input.span8 { 352 | width: 466px; 353 | } 354 | input.span7, textarea.span7, .uneditable-input.span7 { 355 | width: 404px; 356 | } 357 | input.span6, textarea.span6, .uneditable-input.span6 { 358 | width: 342px; 359 | } 360 | input.span5, textarea.span5, .uneditable-input.span5 { 361 | width: 280px; 362 | } 363 | input.span4, textarea.span4, .uneditable-input.span4 { 364 | width: 218px; 365 | } 366 | input.span3, textarea.span3, .uneditable-input.span3 { 367 | width: 156px; 368 | } 369 | input.span2, textarea.span2, .uneditable-input.span2 { 370 | width: 94px; 371 | } 372 | input.span1, textarea.span1, .uneditable-input.span1 { 373 | width: 32px; 374 | } 375 | } 376 | @media (max-width: 979px) { 377 | body { 378 | padding-top: 0; 379 | } 380 | .navbar-fixed-top { 381 | position: static; 382 | margin-bottom: 18px; 383 | } 384 | .navbar-fixed-top .navbar-inner { 385 | padding: 5px; 386 | } 387 | .navbar .container { 388 | width: auto; 389 | padding: 0; 390 | } 391 | .navbar .brand { 392 | padding-left: 10px; 393 | padding-right: 10px; 394 | margin: 0 0 0 -5px; 395 | } 396 | .navbar .nav-collapse { 397 | clear: left; 398 | } 399 | .navbar .nav { 400 | float: none; 401 | margin: 0 0 9px; 402 | } 403 | .navbar .nav > li { 404 | float: none; 405 | } 406 | .navbar .nav > li > a { 407 | margin-bottom: 2px; 408 | } 409 | .navbar .nav > .divider-vertical { 410 | display: none; 411 | } 412 | .navbar .nav .nav-header { 413 | color: #999999; 414 | text-shadow: none; 415 | } 416 | .navbar .nav > li > a, 417 | .navbar .dropdown-menu a { 418 | padding: 6px 15px; 419 | font-weight: bold; 420 | color: #999999; 421 | -webkit-border-radius: 3px; 422 | -moz-border-radius: 3px; 423 | border-radius: 3px; 424 | } 425 | .navbar .dropdown-menu li + li a { 426 | margin-bottom: 2px; 427 | } 428 | .navbar .nav > li > a:hover, 429 | .navbar .dropdown-menu a:hover { 430 | background-color: #222222; 431 | } 432 | .navbar .dropdown-menu { 433 | position: static; 434 | top: auto; 435 | left: auto; 436 | float: none; 437 | display: block; 438 | max-width: none; 439 | margin: 0 15px; 440 | padding: 0; 441 | background-color: transparent; 442 | border: none; 443 | -webkit-border-radius: 0; 444 | -moz-border-radius: 0; 445 | border-radius: 0; 446 | -webkit-box-shadow: none; 447 | -moz-box-shadow: none; 448 | box-shadow: none; 449 | } 450 | .navbar .dropdown-menu:before, 451 | .navbar .dropdown-menu:after { 452 | display: none; 453 | } 454 | .navbar .dropdown-menu .divider { 455 | display: none; 456 | } 457 | .navbar-form, 458 | .navbar-search { 459 | float: none; 460 | padding: 9px 15px; 461 | margin: 9px 0; 462 | border-top: 1px solid #222222; 463 | border-bottom: 1px solid #222222; 464 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 465 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 466 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 467 | } 468 | .navbar .nav.pull-right { 469 | float: none; 470 | margin-left: 0; 471 | } 472 | .navbar-static .navbar-inner { 473 | padding-left: 10px; 474 | padding-right: 10px; 475 | } 476 | .btn-navbar { 477 | display: block; 478 | } 479 | .nav-collapse { 480 | overflow: hidden; 481 | height: 0; 482 | } 483 | } 484 | @media (min-width: 980px) { 485 | .nav-collapse.collapse { 486 | height: auto !important; 487 | overflow: visible !important; 488 | } 489 | } 490 | @media (min-width: 1200px) { 491 | .row { 492 | margin-left: -30px; 493 | *zoom: 1; 494 | } 495 | .row:before, 496 | .row:after { 497 | display: table; 498 | content: ""; 499 | } 500 | .row:after { 501 | clear: both; 502 | } 503 | [class*="span"] { 504 | float: left; 505 | margin-left: 30px; 506 | } 507 | .container, 508 | .navbar-fixed-top .container, 509 | .navbar-fixed-bottom .container { 510 | width: 1170px; 511 | } 512 | .span12 { 513 | width: 1170px; 514 | } 515 | .span11 { 516 | width: 1070px; 517 | } 518 | .span10 { 519 | width: 970px; 520 | } 521 | .span9 { 522 | width: 870px; 523 | } 524 | .span8 { 525 | width: 770px; 526 | } 527 | .span7 { 528 | width: 670px; 529 | } 530 | .span6 { 531 | width: 570px; 532 | } 533 | .span5 { 534 | width: 470px; 535 | } 536 | .span4 { 537 | width: 370px; 538 | } 539 | .span3 { 540 | width: 270px; 541 | } 542 | .span2 { 543 | width: 170px; 544 | } 545 | .span1 { 546 | width: 70px; 547 | } 548 | .offset12 { 549 | margin-left: 1230px; 550 | } 551 | .offset11 { 552 | margin-left: 1130px; 553 | } 554 | .offset10 { 555 | margin-left: 1030px; 556 | } 557 | .offset9 { 558 | margin-left: 930px; 559 | } 560 | .offset8 { 561 | margin-left: 830px; 562 | } 563 | .offset7 { 564 | margin-left: 730px; 565 | } 566 | .offset6 { 567 | margin-left: 630px; 568 | } 569 | .offset5 { 570 | margin-left: 530px; 571 | } 572 | .offset4 { 573 | margin-left: 430px; 574 | } 575 | .offset3 { 576 | margin-left: 330px; 577 | } 578 | .offset2 { 579 | margin-left: 230px; 580 | } 581 | .offset1 { 582 | margin-left: 130px; 583 | } 584 | .row-fluid { 585 | width: 100%; 586 | *zoom: 1; 587 | } 588 | .row-fluid:before, 589 | .row-fluid:after { 590 | display: table; 591 | content: ""; 592 | } 593 | .row-fluid:after { 594 | clear: both; 595 | } 596 | .row-fluid > [class*="span"] { 597 | float: left; 598 | margin-left: 2.564102564%; 599 | } 600 | .row-fluid > [class*="span"]:first-child { 601 | margin-left: 0; 602 | } 603 | .row-fluid > .span12 { 604 | width: 100%; 605 | } 606 | .row-fluid > .span11 { 607 | width: 91.45299145300001%; 608 | } 609 | .row-fluid > .span10 { 610 | width: 82.905982906%; 611 | } 612 | .row-fluid > .span9 { 613 | width: 74.358974359%; 614 | } 615 | .row-fluid > .span8 { 616 | width: 65.81196581200001%; 617 | } 618 | .row-fluid > .span7 { 619 | width: 57.264957265%; 620 | } 621 | .row-fluid > .span6 { 622 | width: 48.717948718%; 623 | } 624 | .row-fluid > .span5 { 625 | width: 40.170940171000005%; 626 | } 627 | .row-fluid > .span4 { 628 | width: 31.623931624%; 629 | } 630 | .row-fluid > .span3 { 631 | width: 23.076923077%; 632 | } 633 | .row-fluid > .span2 { 634 | width: 14.529914530000001%; 635 | } 636 | .row-fluid > .span1 { 637 | width: 5.982905983%; 638 | } 639 | input, 640 | textarea, 641 | .uneditable-input { 642 | margin-left: 0; 643 | } 644 | input.span12, textarea.span12, .uneditable-input.span12 { 645 | width: 1160px; 646 | } 647 | input.span11, textarea.span11, .uneditable-input.span11 { 648 | width: 1060px; 649 | } 650 | input.span10, textarea.span10, .uneditable-input.span10 { 651 | width: 960px; 652 | } 653 | input.span9, textarea.span9, .uneditable-input.span9 { 654 | width: 860px; 655 | } 656 | input.span8, textarea.span8, .uneditable-input.span8 { 657 | width: 760px; 658 | } 659 | input.span7, textarea.span7, .uneditable-input.span7 { 660 | width: 660px; 661 | } 662 | input.span6, textarea.span6, .uneditable-input.span6 { 663 | width: 560px; 664 | } 665 | input.span5, textarea.span5, .uneditable-input.span5 { 666 | width: 460px; 667 | } 668 | input.span4, textarea.span4, .uneditable-input.span4 { 669 | width: 360px; 670 | } 671 | input.span3, textarea.span3, .uneditable-input.span3 { 672 | width: 260px; 673 | } 674 | input.span2, textarea.span2, .uneditable-input.span2 { 675 | width: 160px; 676 | } 677 | input.span1, textarea.span1, .uneditable-input.span1 { 678 | width: 60px; 679 | } 680 | .thumbnails { 681 | margin-left: -30px; 682 | } 683 | .thumbnails > li { 684 | margin-left: 30px; 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /gitube/static/css/gitweb.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | font-size: small; 4 | border: solid #d9d8d1; 5 | border-width: 1px; 6 | margin: 10px; 7 | background-color: #ffffff; 8 | color: #000000; 9 | } 10 | 11 | a { 12 | color: #0000cc; 13 | } 14 | 15 | a:hover, a:visited, a:active { 16 | color: #880000; 17 | } 18 | 19 | span.cntrl { 20 | border: dashed #aaaaaa; 21 | border-width: 1px; 22 | padding: 0px 2px 0px 2px; 23 | margin: 0px 2px 0px 2px; 24 | } 25 | 26 | img.logo { 27 | float: right; 28 | border-width: 0px; 29 | } 30 | 31 | img.avatar { 32 | vertical-align: middle; 33 | } 34 | 35 | a.list img.avatar { 36 | border-style: none; 37 | } 38 | 39 | div.page_header { 40 | height: 25px; 41 | padding: 8px; 42 | font-size: 150%; 43 | font-weight: bold; 44 | background-color: #d9d8d1; 45 | } 46 | 47 | div.page_header a:visited, a.header { 48 | color: #0000cc; 49 | } 50 | 51 | div.page_header a:hover { 52 | color: #880000; 53 | } 54 | 55 | div.page_nav { 56 | padding: 8px; 57 | } 58 | 59 | div.page_nav a:visited { 60 | color: #0000cc; 61 | } 62 | 63 | div.page_path { 64 | padding: 8px; 65 | font-weight: bold; 66 | border: solid #d9d8d1; 67 | border-width: 0px 0px 1px; 68 | } 69 | 70 | div.page_footer { 71 | height: 17px; 72 | padding: 4px 8px; 73 | background-color: #d9d8d1; 74 | } 75 | 76 | div.page_footer_text { 77 | float: left; 78 | color: #555555; 79 | font-style: italic; 80 | } 81 | 82 | div#generating_info { 83 | margin: 4px; 84 | font-size: smaller; 85 | text-align: center; 86 | color: #505050; 87 | } 88 | 89 | div.page_body { 90 | padding: 8px; 91 | font-family: monospace; 92 | } 93 | 94 | div.title, a.title { 95 | display: block; 96 | padding: 6px 8px; 97 | font-weight: bold; 98 | background-color: #edece6; 99 | text-decoration: none; 100 | color: #000000; 101 | } 102 | 103 | div.readme { 104 | padding: 8px; 105 | } 106 | 107 | a.title:hover { 108 | background-color: #d9d8d1; 109 | } 110 | 111 | div.title_text { 112 | padding: 6px 0px; 113 | border: solid #d9d8d1; 114 | border-width: 0px 0px 1px; 115 | font-family: monospace; 116 | } 117 | 118 | div.log_body { 119 | padding: 8px 8px 8px 150px; 120 | } 121 | 122 | span.age { 123 | position: relative; 124 | float: left; 125 | width: 142px; 126 | font-style: italic; 127 | } 128 | 129 | span.signoff { 130 | color: #888888; 131 | } 132 | 133 | div.log_link { 134 | padding: 0px 8px; 135 | font-size: 70%; 136 | font-family: sans-serif; 137 | font-style: normal; 138 | position: relative; 139 | float: left; 140 | width: 136px; 141 | } 142 | 143 | div.list_head { 144 | padding: 6px 8px 4px; 145 | border: solid #d9d8d1; 146 | border-width: 1px 0px 0px; 147 | font-style: italic; 148 | } 149 | 150 | .author_date, .author { 151 | font-style: italic; 152 | } 153 | 154 | div.author_date { 155 | padding: 8px; 156 | border: solid #d9d8d1; 157 | border-width: 0px 0px 1px 0px; 158 | } 159 | 160 | a.list { 161 | text-decoration: none; 162 | color: #000000; 163 | } 164 | 165 | a.subject, a.name { 166 | font-weight: bold; 167 | } 168 | 169 | table.tags a.subject { 170 | font-weight: normal; 171 | } 172 | 173 | a.list:hover { 174 | text-decoration: underline; 175 | color: #880000; 176 | } 177 | 178 | a.text { 179 | text-decoration: none; 180 | color: #0000cc; 181 | } 182 | 183 | a.text:visited { 184 | text-decoration: none; 185 | color: #880000; 186 | } 187 | 188 | a.text:hover { 189 | text-decoration: underline; 190 | color: #880000; 191 | } 192 | 193 | table { 194 | padding: 8px 4px; 195 | border-spacing: 0; 196 | } 197 | 198 | table.diff_tree { 199 | font-family: monospace; 200 | } 201 | 202 | table.combined.diff_tree th { 203 | text-align: center; 204 | } 205 | 206 | table.combined.diff_tree td { 207 | padding-right: 24px; 208 | } 209 | 210 | table.combined.diff_tree th.link, 211 | table.combined.diff_tree td.link { 212 | padding: 0px 2px; 213 | } 214 | 215 | table.combined.diff_tree td.nochange a { 216 | color: #6666ff; 217 | } 218 | 219 | table.combined.diff_tree td.nochange a:hover, 220 | table.combined.diff_tree td.nochange a:visited { 221 | color: #d06666; 222 | } 223 | 224 | table.blame { 225 | border-collapse: collapse; 226 | } 227 | 228 | table.blame td { 229 | padding: 0px 5px; 230 | font-size: 100%; 231 | vertical-align: top; 232 | } 233 | 234 | th { 235 | padding: 2px 5px; 236 | font-size: 100%; 237 | text-align: left; 238 | } 239 | 240 | /* do not change row style on hover for 'blame' view */ 241 | tr.light, 242 | table.blame .light:hover { 243 | background-color: #ffffff; 244 | } 245 | 246 | tr.dark, 247 | table.blame .dark:hover { 248 | background-color: #f6f6f0; 249 | } 250 | 251 | /* currently both use the same, but it can change */ 252 | tr.light:hover, 253 | tr.dark:hover { 254 | background-color: #edece6; 255 | } 256 | 257 | /* boundary commits in 'blame' view */ 258 | /* and commits without "previous" */ 259 | tr.boundary td.sha1, 260 | tr.no-previous td.linenr { 261 | font-weight: bold; 262 | } 263 | 264 | /* for 'blame_incremental', during processing */ 265 | tr.color1 { background-color: #f6fff6; } 266 | tr.color2 { background-color: #f6f6ff; } 267 | tr.color3 { background-color: #fff6f6; } 268 | 269 | td { 270 | padding: 2px 5px; 271 | font-size: 100%; 272 | vertical-align: top; 273 | } 274 | 275 | td.link, td.selflink { 276 | padding: 2px 5px; 277 | font-family: sans-serif; 278 | font-size: 70%; 279 | } 280 | 281 | td.selflink { 282 | padding-right: 0px; 283 | } 284 | 285 | td.sha1 { 286 | font-family: monospace; 287 | } 288 | 289 | .error { 290 | color: red; 291 | background-color: yellow; 292 | } 293 | 294 | td.current_head { 295 | text-decoration: underline; 296 | } 297 | 298 | table.diff_tree span.file_status.new { 299 | color: #008000; 300 | } 301 | 302 | table.diff_tree span.file_status.deleted { 303 | color: #c00000; 304 | } 305 | 306 | table.diff_tree span.file_status.moved, 307 | table.diff_tree span.file_status.mode_chnge { 308 | color: #777777; 309 | } 310 | 311 | table.diff_tree span.file_status.copied { 312 | color: #70a070; 313 | } 314 | 315 | /* noage: "No commits" */ 316 | table.project_list td.noage { 317 | color: #808080; 318 | font-style: italic; 319 | } 320 | 321 | /* age2: 60*60*24*2 <= age */ 322 | table.project_list td.age2, table.blame td.age2 { 323 | font-style: italic; 324 | } 325 | 326 | /* age1: 60*60*2 <= age < 60*60*24*2 */ 327 | table.project_list td.age1 { 328 | color: #009900; 329 | font-style: italic; 330 | } 331 | 332 | table.blame td.age1 { 333 | color: #009900; 334 | background: transparent; 335 | } 336 | 337 | /* age0: age < 60*60*2 */ 338 | table.project_list td.age0 { 339 | color: #009900; 340 | font-style: italic; 341 | font-weight: bold; 342 | } 343 | 344 | table.blame td.age0 { 345 | color: #009900; 346 | background: transparent; 347 | font-weight: bold; 348 | } 349 | 350 | td.pre, div.pre, div.diff { 351 | font-family: monospace; 352 | font-size: 12px; 353 | white-space: pre; 354 | } 355 | 356 | td.mode { 357 | font-family: monospace; 358 | } 359 | 360 | /* progress of blame_interactive */ 361 | div#progress_bar { 362 | height: 2px; 363 | margin-bottom: -2px; 364 | background-color: #d8d9d0; 365 | } 366 | div#progress_info { 367 | float: right; 368 | text-align: right; 369 | } 370 | 371 | /* format of (optional) objects size in 'tree' view */ 372 | td.size { 373 | font-family: monospace; 374 | text-align: right; 375 | } 376 | 377 | /* styling of diffs (patchsets): commitdiff and blobdiff views */ 378 | div.diff.header, 379 | div.diff.extended_header { 380 | white-space: normal; 381 | } 382 | 383 | div.diff.header { 384 | font-weight: bold; 385 | 386 | background-color: #edece6; 387 | 388 | margin-top: 4px; 389 | padding: 4px 0px 2px 0px; 390 | border: solid #d9d8d1; 391 | border-width: 1px 0px 1px 0px; 392 | } 393 | 394 | div.diff.header a.path { 395 | text-decoration: underline; 396 | } 397 | 398 | div.diff.extended_header, 399 | div.diff.extended_header a.path, 400 | div.diff.extended_header a.hash { 401 | color: #777777; 402 | } 403 | 404 | div.diff.extended_header .info { 405 | color: #b0b0b0; 406 | } 407 | 408 | div.diff.extended_header { 409 | background-color: #f6f5ee; 410 | padding: 2px 0px 2px 0px; 411 | } 412 | 413 | div.diff a.list, 414 | div.diff a.path, 415 | div.diff a.hash { 416 | text-decoration: none; 417 | } 418 | 419 | div.diff a.list:hover, 420 | div.diff a.path:hover, 421 | div.diff a.hash:hover { 422 | text-decoration: underline; 423 | } 424 | 425 | div.diff.to_file a.path, 426 | div.diff.to_file { 427 | color: #007000; 428 | } 429 | 430 | div.diff.add { 431 | color: #008800; 432 | } 433 | 434 | div.diff.from_file a.path, 435 | div.diff.from_file { 436 | color: #aa0000; 437 | } 438 | 439 | div.diff.rem { 440 | color: #cc0000; 441 | } 442 | 443 | div.diff.chunk_header a, 444 | div.diff.chunk_header { 445 | color: #990099; 446 | } 447 | 448 | div.diff.chunk_header { 449 | border: dotted #ffe0ff; 450 | border-width: 1px 0px 0px 0px; 451 | margin-top: 2px; 452 | } 453 | 454 | div.diff.chunk_header span.chunk_info { 455 | background-color: #ffeeff; 456 | } 457 | 458 | div.diff.chunk_header span.section { 459 | color: #aa22aa; 460 | } 461 | 462 | div.diff.incomplete { 463 | color: #cccccc; 464 | } 465 | 466 | div.diff.nodifferences { 467 | font-weight: bold; 468 | color: #600000; 469 | } 470 | 471 | div.index_include { 472 | border: solid #d9d8d1; 473 | border-width: 0px 0px 1px; 474 | padding: 12px 8px; 475 | } 476 | 477 | div.search { 478 | font-size: 100%; 479 | font-weight: normal; 480 | margin: 4px 8px; 481 | float: right; 482 | top: 56px; 483 | right: 12px 484 | } 485 | 486 | p.projsearch { 487 | text-align: center; 488 | } 489 | 490 | td.linenr { 491 | text-align: right; 492 | } 493 | 494 | a.linenr { 495 | color: #999999; 496 | text-decoration: none 497 | } 498 | 499 | a.rss_logo { 500 | float: right; 501 | padding: 3px 0px; 502 | width: 35px; 503 | line-height: 10px; 504 | border: 1px solid; 505 | border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e; 506 | color: #ffffff; 507 | background-color: #ff6600; 508 | font-weight: bold; 509 | font-family: sans-serif; 510 | font-size: 70%; 511 | text-align: center; 512 | text-decoration: none; 513 | } 514 | 515 | a.rss_logo:hover { 516 | background-color: #ee5500; 517 | } 518 | 519 | a.rss_logo.generic { 520 | background-color: #ff8800; 521 | } 522 | 523 | a.rss_logo.generic:hover { 524 | background-color: #ee7700; 525 | } 526 | 527 | span.refs span { 528 | padding: 0px 4px; 529 | font-size: 70%; 530 | font-weight: normal; 531 | border: 1px solid; 532 | background-color: #ffaaff; 533 | border-color: #ffccff #ff00ee #ff00ee #ffccff; 534 | } 535 | 536 | span.refs span a { 537 | text-decoration: none; 538 | color: inherit; 539 | } 540 | 541 | span.refs span a:hover { 542 | text-decoration: underline; 543 | } 544 | 545 | span.refs span.indirect { 546 | font-style: italic; 547 | } 548 | 549 | span.refs span.ref { 550 | background-color: #aaaaff; 551 | border-color: #ccccff #0033cc #0033cc #ccccff; 552 | } 553 | 554 | span.refs span.tag { 555 | background-color: #ffffaa; 556 | border-color: #ffffcc #ffee00 #ffee00 #ffffcc; 557 | } 558 | 559 | span.refs span.head { 560 | background-color: #aaffaa; 561 | border-color: #ccffcc #00cc33 #00cc33 #ccffcc; 562 | } 563 | 564 | span.atnight { 565 | color: #cc0000; 566 | } 567 | 568 | span.match { 569 | color: #e00000; 570 | } 571 | 572 | div.binary { 573 | font-style: italic; 574 | } 575 | -------------------------------------------------------------------------------- /gitube/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | font-family: Arial, Verdana, SunSans-Regular, Sans-Serif; 4 | padding-top: 60px; 5 | background: whiteSmoke; 6 | } 7 | 8 | /* links */ 9 | a { 10 | color:#366EB8; 11 | text-decoration:none; 12 | } 13 | a:hover { 14 | text-decoration:underline; 15 | } 16 | 17 | /* -----------------content--------------------- */ 18 | #content { 19 | padding: 10px 20px; 20 | width: 560px; 21 | float: left; 22 | border-right:thin solid #AAAAAA; 23 | } 24 | 25 | h1.title { 26 | font: 26px bold; 27 | } 28 | 29 | .action { 30 | float: right; 31 | } 32 | 33 | .project-desc { 34 | color: #888888; 35 | } 36 | 37 | .errorlist { 38 | color: #FF0000; 39 | } 40 | 41 | .repo-list li { 42 | border: 1px solid #CECECE; 43 | background: white; 44 | padding: 5px 10px 15px 10px; 45 | margin-bottom: 10px; 46 | } 47 | 48 | .repo-list h3 { 49 | font-weight: bold; 50 | } 51 | 52 | pre, code { 53 | font-family: Inconsolata, Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace; 54 | } 55 | 56 | /*gbox*/ 57 | 58 | .gbox { 59 | border: 1px solid #CECECE; 60 | background: white; 61 | border-radius: 5px; 62 | -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1); 63 | -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.1); 64 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 65 | } 66 | 67 | .gbox .box-content { 68 | padding: 15px; 69 | } 70 | 71 | /* -----------footer--------------------------- */ 72 | #footer { 73 | margin: 0px; 74 | padding: 5px; 75 | text-align: center; 76 | background-color: #EEEEEE; 77 | border-radius: 6px; 78 | -moz-border-radius: 6px; 79 | -webkit-border-radius: 6px; 80 | } 81 | -------------------------------------------------------------------------------- /gitube/static/fancybox/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/blank.gif -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_close.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_loading.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_nav_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_nav_left.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_nav_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_nav_right.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_e.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_n.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_ne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_ne.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_nw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_nw.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_s.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_se.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_se.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_sw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_sw.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_shadow_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_shadow_w.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_title_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_title_left.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_title_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_title_main.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_title_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_title_over.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancy_title_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancy_title_right.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancybox-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancybox-x.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancybox-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancybox-y.png -------------------------------------------------------------------------------- /gitube/static/fancybox/fancybox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/fancybox/fancybox.png -------------------------------------------------------------------------------- /gitube/static/fancybox/jquery.fancybox-1.3.4.css: -------------------------------------------------------------------------------- 1 | /* 2 | * FancyBox - jQuery Plugin 3 | * Simple and fancy lightbox alternative 4 | * 5 | * Examples and documentation at: http://fancybox.net 6 | * 7 | * Copyright (c) 2008 - 2010 Janis Skarnelis 8 | * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. 9 | * 10 | * Version: 1.3.4 (11/11/2010) 11 | * Requires: jQuery v1.3+ 12 | * 13 | * Dual licensed under the MIT and GPL licenses: 14 | * http://www.opensource.org/licenses/mit-license.php 15 | * http://www.gnu.org/licenses/gpl.html 16 | */ 17 | 18 | #fancybox-loading { 19 | position: fixed; 20 | top: 50%; 21 | left: 50%; 22 | width: 40px; 23 | height: 40px; 24 | margin-top: -20px; 25 | margin-left: -20px; 26 | cursor: pointer; 27 | overflow: hidden; 28 | z-index: 1104; 29 | display: none; 30 | } 31 | 32 | #fancybox-loading div { 33 | position: absolute; 34 | top: 0; 35 | left: 0; 36 | width: 40px; 37 | height: 480px; 38 | background-image: url('fancybox.png'); 39 | } 40 | 41 | #fancybox-overlay { 42 | position: absolute; 43 | top: 0; 44 | left: 0; 45 | width: 100%; 46 | z-index: 1100; 47 | display: none; 48 | } 49 | 50 | #fancybox-tmp { 51 | padding: 0; 52 | margin: 0; 53 | border: 0; 54 | overflow: auto; 55 | display: none; 56 | } 57 | 58 | #fancybox-wrap { 59 | position: absolute; 60 | top: 0; 61 | left: 0; 62 | padding: 20px; 63 | z-index: 1101; 64 | outline: none; 65 | display: none; 66 | } 67 | 68 | #fancybox-outer { 69 | position: relative; 70 | width: 100%; 71 | height: 100%; 72 | background: #fff; 73 | } 74 | 75 | #fancybox-content { 76 | width: 0; 77 | height: 0; 78 | padding: 0; 79 | outline: none; 80 | position: relative; 81 | overflow: hidden; 82 | z-index: 1102; 83 | border: 0px solid #fff; 84 | } 85 | 86 | #fancybox-hide-sel-frame { 87 | position: absolute; 88 | top: 0; 89 | left: 0; 90 | width: 100%; 91 | height: 100%; 92 | background: transparent; 93 | z-index: 1101; 94 | } 95 | 96 | #fancybox-close { 97 | position: absolute; 98 | top: -15px; 99 | right: -15px; 100 | width: 30px; 101 | height: 30px; 102 | background: transparent url('fancybox.png') -40px 0px; 103 | cursor: pointer; 104 | z-index: 1103; 105 | display: none; 106 | } 107 | 108 | #fancybox-error { 109 | color: #444; 110 | font: normal 12px/20px Arial; 111 | padding: 14px; 112 | margin: 0; 113 | } 114 | 115 | #fancybox-img { 116 | width: 100%; 117 | height: 100%; 118 | padding: 0; 119 | margin: 0; 120 | border: none; 121 | outline: none; 122 | line-height: 0; 123 | vertical-align: top; 124 | } 125 | 126 | #fancybox-frame { 127 | width: 100%; 128 | height: 100%; 129 | border: none; 130 | display: block; 131 | } 132 | 133 | #fancybox-left, #fancybox-right { 134 | position: absolute; 135 | bottom: 0px; 136 | height: 100%; 137 | width: 35%; 138 | cursor: pointer; 139 | outline: none; 140 | background: transparent url('blank.gif'); 141 | z-index: 1102; 142 | display: none; 143 | } 144 | 145 | #fancybox-left { 146 | left: 0px; 147 | } 148 | 149 | #fancybox-right { 150 | right: 0px; 151 | } 152 | 153 | #fancybox-left-ico, #fancybox-right-ico { 154 | position: absolute; 155 | top: 50%; 156 | left: -9999px; 157 | width: 30px; 158 | height: 30px; 159 | margin-top: -15px; 160 | cursor: pointer; 161 | z-index: 1102; 162 | display: block; 163 | } 164 | 165 | #fancybox-left-ico { 166 | background-image: url('fancybox.png'); 167 | background-position: -40px -30px; 168 | } 169 | 170 | #fancybox-right-ico { 171 | background-image: url('fancybox.png'); 172 | background-position: -40px -60px; 173 | } 174 | 175 | #fancybox-left:hover, #fancybox-right:hover { 176 | visibility: visible; /* IE6 */ 177 | } 178 | 179 | #fancybox-left:hover span { 180 | left: 20px; 181 | } 182 | 183 | #fancybox-right:hover span { 184 | left: auto; 185 | right: 20px; 186 | } 187 | 188 | .fancybox-bg { 189 | position: absolute; 190 | padding: 0; 191 | margin: 0; 192 | border: 0; 193 | width: 20px; 194 | height: 20px; 195 | z-index: 1001; 196 | } 197 | 198 | #fancybox-bg-n { 199 | top: -20px; 200 | left: 0; 201 | width: 100%; 202 | background-image: url('fancybox-x.png'); 203 | } 204 | 205 | #fancybox-bg-ne { 206 | top: -20px; 207 | right: -20px; 208 | background-image: url('fancybox.png'); 209 | background-position: -40px -162px; 210 | } 211 | 212 | #fancybox-bg-e { 213 | top: 0; 214 | right: -20px; 215 | height: 100%; 216 | background-image: url('fancybox-y.png'); 217 | background-position: -20px 0px; 218 | } 219 | 220 | #fancybox-bg-se { 221 | bottom: -20px; 222 | right: -20px; 223 | background-image: url('fancybox.png'); 224 | background-position: -40px -182px; 225 | } 226 | 227 | #fancybox-bg-s { 228 | bottom: -20px; 229 | left: 0; 230 | width: 100%; 231 | background-image: url('fancybox-x.png'); 232 | background-position: 0px -20px; 233 | } 234 | 235 | #fancybox-bg-sw { 236 | bottom: -20px; 237 | left: -20px; 238 | background-image: url('fancybox.png'); 239 | background-position: -40px -142px; 240 | } 241 | 242 | #fancybox-bg-w { 243 | top: 0; 244 | left: -20px; 245 | height: 100%; 246 | background-image: url('fancybox-y.png'); 247 | } 248 | 249 | #fancybox-bg-nw { 250 | top: -20px; 251 | left: -20px; 252 | background-image: url('fancybox.png'); 253 | background-position: -40px -122px; 254 | } 255 | 256 | #fancybox-title { 257 | font-family: Helvetica; 258 | font-size: 12px; 259 | z-index: 1102; 260 | } 261 | 262 | .fancybox-title-inside { 263 | padding-bottom: 10px; 264 | text-align: center; 265 | color: #333; 266 | background: #fff; 267 | position: relative; 268 | } 269 | 270 | .fancybox-title-outside { 271 | padding-top: 10px; 272 | color: #fff; 273 | } 274 | 275 | .fancybox-title-over { 276 | position: absolute; 277 | bottom: 0; 278 | left: 0; 279 | color: #FFF; 280 | text-align: left; 281 | } 282 | 283 | #fancybox-title-over { 284 | padding: 10px; 285 | background-image: url('fancy_title_over.png'); 286 | display: block; 287 | } 288 | 289 | .fancybox-title-float { 290 | position: absolute; 291 | left: 0; 292 | bottom: -20px; 293 | height: 32px; 294 | } 295 | 296 | #fancybox-title-float-wrap { 297 | border: none; 298 | border-collapse: collapse; 299 | width: auto; 300 | } 301 | 302 | #fancybox-title-float-wrap td { 303 | border: none; 304 | white-space: nowrap; 305 | } 306 | 307 | #fancybox-title-float-left { 308 | padding: 0 0 0 15px; 309 | background: url('fancybox.png') -40px -90px no-repeat; 310 | } 311 | 312 | #fancybox-title-float-main { 313 | color: #FFF; 314 | line-height: 29px; 315 | font-weight: bold; 316 | padding: 0 0 3px 0; 317 | background: url('fancybox-x.png') 0px -40px; 318 | } 319 | 320 | #fancybox-title-float-right { 321 | padding: 0 0 0 15px; 322 | background: url('fancybox.png') -55px -90px no-repeat; 323 | } 324 | 325 | /* IE6 */ 326 | 327 | .fancybox-ie6 #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_close.png', sizingMethod='scale'); } 328 | 329 | .fancybox-ie6 #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_left.png', sizingMethod='scale'); } 330 | .fancybox-ie6 #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_right.png', sizingMethod='scale'); } 331 | 332 | .fancybox-ie6 #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } 333 | .fancybox-ie6 #fancybox-title-float-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_left.png', sizingMethod='scale'); } 334 | .fancybox-ie6 #fancybox-title-float-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_main.png', sizingMethod='scale'); } 335 | .fancybox-ie6 #fancybox-title-float-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_right.png', sizingMethod='scale'); } 336 | 337 | .fancybox-ie6 #fancybox-bg-w, .fancybox-ie6 #fancybox-bg-e, .fancybox-ie6 #fancybox-left, .fancybox-ie6 #fancybox-right, #fancybox-hide-sel-frame { 338 | height: expression(this.parentNode.clientHeight + "px"); 339 | } 340 | 341 | #fancybox-loading.fancybox-ie6 { 342 | position: absolute; margin-top: 0; 343 | top: expression( (-20 + (document.documentElement.clientHeight ? document.documentElement.clientHeight/2 : document.body.clientHeight/2 ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop )) + 'px'); 344 | } 345 | 346 | #fancybox-loading.fancybox-ie6 div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_loading.png', sizingMethod='scale'); } 347 | 348 | /* IE6, IE7, IE8 */ 349 | 350 | .fancybox-ie .fancybox-bg { background: transparent !important; } 351 | 352 | .fancybox-ie #fancybox-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); } 353 | .fancybox-ie #fancybox-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); } 354 | .fancybox-ie #fancybox-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); } 355 | .fancybox-ie #fancybox-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); } 356 | .fancybox-ie #fancybox-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); } 357 | .fancybox-ie #fancybox-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); } 358 | .fancybox-ie #fancybox-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); } 359 | .fancybox-ie #fancybox-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); } -------------------------------------------------------------------------------- /gitube/static/fancybox/jquery.fancybox-1.3.4.pack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * FancyBox - jQuery Plugin 3 | * Simple and fancy lightbox alternative 4 | * 5 | * Examples and documentation at: http://fancybox.net 6 | * 7 | * Copyright (c) 2008 - 2010 Janis Skarnelis 8 | * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. 9 | * 10 | * Version: 1.3.4 (11/11/2010) 11 | * Requires: jQuery v1.3+ 12 | * 13 | * Dual licensed under the MIT and GPL licenses: 14 | * http://www.opensource.org/licenses/mit-license.php 15 | * http://www.gnu.org/licenses/gpl.html 16 | */ 17 | 18 | ;(function(b){var m,t,u,f,D,j,E,n,z,A,q=0,e={},o=[],p=0,d={},l=[],G=null,v=new Image,J=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,W=/[^\.]\.(swf)\s*$/i,K,L=1,y=0,s="",r,i,h=false,B=b.extend(b("
")[0],{prop:0}),M=b.browser.msie&&b.browser.version<7&&!window.XMLHttpRequest,N=function(){t.hide();v.onerror=v.onload=null;G&&G.abort();m.empty()},O=function(){if(false===e.onError(o,q,e)){t.hide();h=false}else{e.titleShow=false;e.width="auto";e.height="auto";m.html('

The requested content cannot be loaded.
Please try again later.

'); 19 | F()}},I=function(){var a=o[q],c,g,k,C,P,w;N();e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));w=e.onStart(o,q,e);if(w===false)h=false;else{if(typeof w=="object")e=b.extend(e,w);k=e.title||(a.nodeName?b(a).attr("title"):a.title)||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(k===""&&e.orig&&e.titleFromAlt)k=e.orig.attr("alt");c=e.href||(a.nodeName?b(a).attr("href"):a.href)||null;if(/^(?:javascript)/i.test(c)|| 20 | c=="#")c=null;if(e.type){g=e.type;if(!c)c=e.content}else if(e.content)g="html";else if(c)g=c.match(J)?"image":c.match(W)?"swf":b(a).hasClass("iframe")?"iframe":c.indexOf("#")===0?"inline":"ajax";if(g){if(g=="inline"){a=c.substr(c.indexOf("#"));g=b(a).length>0?"inline":"ajax"}e.type=g;e.href=c;e.title=k;if(e.autoDimensions)if(e.type=="html"||e.type=="inline"||e.type=="ajax"){e.width="auto";e.height="auto"}else e.autoDimensions=false;if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick= 21 | false;e.enableEscapeButton=false;e.showCloseButton=false}e.padding=parseInt(e.padding,10);e.margin=parseInt(e.margin,10);m.css("padding",e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(j.children())});switch(g){case "html":m.html(e.content);F();break;case "inline":if(b(a).parent().is("#fancybox-content")===true){h=false;break}b('
').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(j.children())}).bind("fancybox-cancel", 22 | function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity();v=new Image;v.onerror=function(){O()};v.onload=function(){h=true;v.onerror=v.onload=null;e.width=v.width;e.height=v.height;b("").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='';P="";b.each(e.swf,function(x,H){C+='';P+=" "+x+'="'+H+'"'});C+='";m.html(C);F();break;case "ajax":h=false;b.fancybox.showActivity();e.ajax.win=e.ajax.success;G=b.ajax(b.extend({},e.ajax,{url:c,data:e.ajax.data||{},error:function(x){x.status>0&&O()},success:function(x,H,R){if((typeof R=="object"?R:G).status==200){if(typeof e.ajax.win== 24 | "function"){w=e.ajax.win(c,x,H,R);if(w===false){t.hide();return}else if(typeof w=="string"||typeof w=="object")x=w}m.html(x);F()}}}));break;case "iframe":Q()}}else O()}},F=function(){var a=e.width,c=e.height;a=a.toString().indexOf("%")>-1?parseInt((b(window).width()-e.margin*2)*parseFloat(a)/100,10)+"px":a=="auto"?"auto":a+"px";c=c.toString().indexOf("%")>-1?parseInt((b(window).height()-e.margin*2)*parseFloat(c)/100,10)+"px":c=="auto"?"auto":c+"px";m.wrapInner('
');e.width=m.width();e.height=m.height();Q()},Q=function(){var a,c;t.hide();if(f.is(":visible")&&false===d.onCleanup(l,p,d)){b.event.trigger("fancybox-cancel");h=false}else{h=true;b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");f.is(":visible")&&d.titlePosition!=="outside"&&f.css("height",f.height());l=o;p=q;d=e;if(d.overlayShow){u.css({"background-color":d.overlayColor, 26 | opacity:d.overlayOpacity,cursor:d.hideOnOverlayClick?"pointer":"auto",height:b(document).height()});if(!u.is(":visible")){M&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"});u.show()}}else u.hide();i=X();s=d.title||"";y=0;n.empty().removeAttr("style").removeClass();if(d.titleShow!==false){if(b.isFunction(d.titleFormat))a=d.titleFormat(s,l,p,d);else a=s&&s.length? 27 | d.titlePosition=="float"?'
'+s+'
':'
'+s+"
":false;s=a;if(!(!s||s==="")){n.addClass("fancybox-title-"+d.titlePosition).html(s).appendTo("body").show();switch(d.titlePosition){case "inside":n.css({width:i.width-d.padding*2,marginLeft:d.padding,marginRight:d.padding}); 28 | y=n.outerHeight(true);n.appendTo(D);i.height+=y;break;case "over":n.css({marginLeft:d.padding,width:i.width-d.padding*2,bottom:d.padding}).appendTo(D);break;case "float":n.css("left",parseInt((n.width()-i.width-40)/2,10)*-1).appendTo(f);break;default:n.css({width:i.width-d.padding*2,paddingLeft:d.padding,paddingRight:d.padding}).appendTo(f)}}}n.hide();if(f.is(":visible")){b(E.add(z).add(A)).hide();a=f.position();r={top:a.top,left:a.left,width:f.width(),height:f.height()};c=r.width==i.width&&r.height== 29 | i.height;j.fadeTo(d.changeFade,0.3,function(){var g=function(){j.html(m.contents()).fadeTo(d.changeFade,1,S)};b.event.trigger("fancybox-change");j.empty().removeAttr("filter").css({"border-width":d.padding,width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2});if(c)g();else{B.prop=0;b(B).animate({prop:1},{duration:d.changeSpeed,easing:d.easingChange,step:T,complete:g})}})}else{f.removeAttr("style");j.css("border-width",d.padding);if(d.transitionIn=="elastic"){r=V();j.html(m.contents()); 30 | f.show();if(d.opacity)i.opacity=0;B.prop=0;b(B).animate({prop:1},{duration:d.speedIn,easing:d.easingIn,step:T,complete:S})}else{d.titlePosition=="inside"&&y>0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode== 31 | 37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto"); 32 | s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('').appendTo(j); 33 | f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c); 34 | j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type== 35 | "image"||e.type=="swf"){g=d.width/d.height;if(c.width>a[0]){c.width=a[0];c.height=parseInt((c.width-k)/g+k,10)}if(c.height>a[1]){c.height=a[1];c.width=parseInt((c.height-k)*g+k,10)}}else{c.width=Math.min(c.width,a[0]);c.height=Math.min(c.height,a[1])}c.top=parseInt(Math.max(a[3]-20,a[3]+(a[1]-c.height-40)*0.5),10);c.left=parseInt(Math.max(a[2]-20,a[2]+(a[0]-c.width-40)*0.5),10);return c},V=function(){var a=e.orig?b(e.orig):false,c={};if(a&&a.length){c=a.offset();c.top+=parseInt(a.css("paddingTop"), 36 | 10)||0;c.left+=parseInt(a.css("paddingLeft"),10)||0;c.top+=parseInt(a.css("border-top-width"),10)||0;c.left+=parseInt(a.css("border-left-width"),10)||0;c.width=a.width();c.height=a.height();c={width:c.width+d.padding*2,height:c.height+d.padding*2,top:c.top-d.padding-20,left:c.left-d.padding-20}}else{a=U();c={width:d.padding*2,height:d.padding*2,top:parseInt(a[3]+a[1]*0.5,10),left:parseInt(a[2]+a[0]*0.5,10)}}return c},Z=function(){if(t.is(":visible")){b("div",t).css("top",L*-40+"px");L=(L+1)%12}else clearInterval(K)}; 37 | b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k= 38 | 0,C=a.length;ko.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+ 39 | 1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h= 40 | true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1; 41 | b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5- 42 | d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('
'),t=b('
'),u=b('
'),f=b('
'));D=b('
').append('
').appendTo(f); 43 | D.append(j=b('
'),E=b(''),n=b('
'),z=b(''),A=b(''));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()}); 44 | b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('').prependTo(D)}}}; 45 | b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing", 46 | easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery); -------------------------------------------------------------------------------- /gitube/static/git-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/git-logo.png -------------------------------------------------------------------------------- /gitube/static/gitweb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/gitweb.js -------------------------------------------------------------------------------- /gitube/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /gitube/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryxu/gitube/383152f8b73186a6f8e2553d587936f0c653f414/gitube/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /gitube/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(a){a(function(){"use strict",a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype={constructor:c,close:function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),e.trigger("close"),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger("close").removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()}},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype={constructor:b,setState:function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},toggle:function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")}},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.carousel.defaults,c),this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(){return this.interval=setInterval(a.proxy(this.next,this),this.options.interval),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(){return clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;return!a.support.transition&&this.$element.hasClass("slide")?(this.$element.trigger("slide"),d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")):(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.trigger("slide"),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})),f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=typeof c=="object"&&c;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find(".in"),e;d&&d.length&&(e=d.data("collapse"),d.collapse("hide"),e||d.data("collapse",null)),this.$element[b](0),this.transition("addClass","show","shown"),this.$element[b](this.$element[0][c])},hide:function(){var a=this.dimension();this.reset(this.$element[a]()),this.transition("removeClass","hide","hidden"),this.$element[a](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.$element.trigger(d)};this.$element.trigger(c)[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('