├── .gitignore ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── install ├── installer.sh └── uninstaller.sh ├── package.json ├── release ├── libexec │ └── git-core │ │ └── git-webui └── share │ └── git-webui │ └── webui │ ├── css │ ├── bootstrap.css │ └── git-webui.css │ ├── img │ ├── branch.svg │ ├── computer.svg │ ├── daemon.svg │ ├── doc │ │ ├── log-commit.png │ │ ├── log-tree.png │ │ └── workspace.png │ ├── file.svg │ ├── folder.svg │ ├── git-icon.png │ ├── git-logo.png │ ├── star.svg │ └── tag.svg │ ├── index.html │ └── js │ ├── bootstrap.min.js │ ├── git-webui.js │ └── jquery.min.js └── src ├── libexec └── git-core │ └── git-webui └── share └── git-webui └── webui ├── css ├── bootstrap.less └── git-webui.less ├── img ├── branch.svg ├── computer.svg ├── daemon.svg ├── doc │ ├── log-commit.png │ ├── log-tree.png │ └── workspace.png ├── file.svg ├── folder.svg ├── git-icon.png ├── git-logo.png ├── star.svg └── tag.svg ├── index.html └── js └── git-webui.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | node_modules/ 4 | bower_components/ 5 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | 5 | copy: { 6 | jquery: { 7 | expand: true, 8 | flatten: true, 9 | src: 'bower_components/jquery/dist/jquery.min.js', 10 | dest: 'dist/share/git-webui/webui/js/', 11 | }, 12 | bootstrap: { 13 | expand: true, 14 | flatten: true, 15 | src: 'bower_components/bootstrap/dist/js/bootstrap.min.js', 16 | dest: 'dist/share/git-webui/webui/js/', 17 | }, 18 | git_webui: { 19 | options: { 20 | mode: true, 21 | }, 22 | expand: true, 23 | cwd: 'src', 24 | src: ['libexec/**', 'share/**', '!**/less', '!**/*.less'], 25 | dest: 'dist', 26 | }, 27 | release: { 28 | options: { 29 | mode: true, 30 | }, 31 | expand: true, 32 | cwd: 'dist', 33 | src: '**', 34 | dest: 'release', 35 | }, 36 | }, 37 | 38 | less: { 39 | options: { 40 | paths: 'bower_components/bootstrap/less', 41 | }, 42 | files: { 43 | expand: true, 44 | cwd: 'src', 45 | src: 'share/git-webui/webui/css/*.less', 46 | dest: 'dist', 47 | ext: '.css', 48 | }, 49 | }, 50 | 51 | shell: { 52 | serve: { 53 | command: './dist/libexec/git-core/git-webui' 54 | }, 55 | }, 56 | 57 | watch: { 58 | scripts: { 59 | files: ['src/libexec/**/*', 'src/share/**/*.js', 'src/share/**/*.html'], 60 | tasks: 'copy:git_webui' 61 | }, 62 | css: { 63 | files: 'src/**/*.less', 64 | tasks: 'less', 65 | }, 66 | }, 67 | 68 | clean: ['dist'], 69 | }); 70 | 71 | grunt.loadNpmTasks('grunt-contrib-copy'); 72 | grunt.loadNpmTasks('grunt-contrib-less'); 73 | grunt.loadNpmTasks('grunt-contrib-clean'); 74 | grunt.loadNpmTasks('grunt-shell'); 75 | grunt.loadNpmTasks('grunt-contrib-watch'); 76 | 77 | grunt.registerTask('copytodist', ['copy:jquery', 'copy:bootstrap', 'copy:git_webui']); 78 | grunt.registerTask('default', ['copytodist', 'less']); 79 | grunt.registerTask('serve', ['default', 'shell:serve']); 80 | grunt.registerTask('release', ['default', 'copy:release']); 81 | }; 82 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Apache License 2 | 3 | ## Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | ### TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | #### 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | #### 2. Grant of Copyright License. 32 | 33 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 34 | 35 | #### 3. Grant of Patent License. 36 | 37 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 38 | 39 | #### 4. Redistribution. 40 | 41 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 42 | 43 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 44 | You must cause any modified files to carry prominent notices stating that You changed the files; and 45 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 46 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 47 | 48 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 49 | 50 | #### 5. Submission of Contributions. 51 | 52 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 53 | 54 | #### 6. Trademarks. 55 | 56 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 57 | 58 | #### 7. Disclaimer of Warranty. 59 | 60 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 61 | 62 | #### 8. Limitation of Liability. 63 | 64 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 65 | 66 | #### 9. Accepting Warranty or Additional Liability. 67 | 68 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 69 | 70 | ### END OF TERMS AND CONDITIONS 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git WebUI 2 | 3 | This git extension is a standalone web based user interface for git repositories. 4 | 5 | It comes with history and tree browsing. You may also use it to commit 6 | as it comes with an UI to review local changes and the ability to stage / unstage code. 7 | 8 | Moreover as git-webui is a web server, your repository is accessible to 9 | other people on the same network. They can clone or pull your code using the 10 | same URL. 11 | 12 | It has very few dependencies, you probably already have them on your 13 | Mac / Linux : git, python, and a web browser. 14 | 15 | ## Installation 16 | 17 | ### Automatic (Do you trust me ?) 18 | 19 | The following command will install git-webui in `$HOME/.git-webui` and add a 20 | `webui` alias to your global `.gitconfig` file. 21 | 22 | *Note for Windows users:* These install scripts work for you too. Run them from your Git-Bash shell. 23 | You need to install [Python](https://www.python.org/downloads/) first. 24 | 25 | Using curl (Mac OS X & Windows): 26 | ``` 27 | curl https://raw.githubusercontent.com/alberthier/git-webui/master/install/installer.sh | bash 28 | ``` 29 | 30 | Using wget (Linux): 31 | ``` 32 | wget -O - https://raw.githubusercontent.com/alberthier/git-webui/master/install/installer.sh | bash 33 | ``` 34 | 35 | Upon installation git-webui will update itself automatically every couple of weeks. 36 | You can deactivate auto-update by removing the `autoupdate = true` line from the 37 | `webui` section of your global `.gitconfig` file. 38 | 39 | ### Manual 40 | 41 | Simply clone the repository and install the alias 42 | 43 | ``` 44 | git clone https://github.com/alberthier/git-webui.git 45 | git config --global alias.webui \!$PWD/git-webui/release/libexec/git-core/git-webui 46 | ``` 47 | 48 | If you want to allow auto-update: 49 | ``` 50 | git config --global webui.autoupdate true 51 | ``` 52 | 53 | ## Usage 54 | 55 | ### Starting 56 | 57 | First cd to any of your project versioned with git 58 | ``` 59 | cd 60 | git webui 61 | ``` 62 | 63 | This will start an embedded HTTP server and open your default browser with the GUI. 64 | 65 | ### History Viewing 66 | 67 | The toolbar on the left shows your branches and tags. The log of the currently selected one is displayed. 68 | 69 | When selecting a revision the diff of this specific commit is displayed in the right panel. 70 | 71 | ![Image of log commit](https://raw.githubusercontent.com/alberthier/git-webui/master/src/share/git-webui/webui/img/doc/log-commit.png) 72 | 73 | On top of the right panel, you can choose 'Tree' to display the versioned content at the specific 74 | revision selected in the left panel. You can browse through directories and display file contents. 75 | 76 | ![Image of log tree](https://raw.githubusercontent.com/alberthier/git-webui/master/src/share/git-webui/webui/img/doc/log-tree.png) 77 | 78 | ### Remote access 79 | 80 | Other people on your network have read-only access to your repository: 81 | they may access to the web interface (without 'Workspace'), clone or pull from your repository. 82 | All this through the same url: 83 | 84 | Clone: 85 | ``` 86 | $ git clone http://:8000/ repo_name 87 | ``` 88 | 89 | Pull: 90 | ``` 91 | $ git pull http://:8000/ 92 | ``` 93 | 94 | ### Commit 95 | 96 | Commits can only be made from localhost. 97 | 98 | ![Image of the workspace](https://raw.githubusercontent.com/alberthier/git-webui/master/src/share/git-webui/webui/img/doc/workspace.png) 99 | 100 | - **Working copy** lists the modified files (compared to the staging area) in your working directory 101 | - **Message** lets you enter a commit message 102 | - **Staging area** lists the modified files (compared to HEAD) in your staging area. These are the changes that will be committed 103 | 104 | The diff view lets you review the differences of the selected file. 105 | You can select code in more fine grained way: 106 | - If the displayed diff is from the working copy, you may stage or cancel the selected lines. 107 | - If the displayed diff is from the staging area, you may unstage the selected lines. 108 | 109 | ## Dependencies 110 | 111 | ### Runtime 112 | 113 | - git (of course :) ) 114 | - python 2.7+ or python 3.0+ (Generally already installed on your Mac / Linux) 115 | - An up-to-date modern browser 116 | 117 | ### Development 118 | 119 | - Runtime dependencies and ... 120 | - node.js 121 | - grunt-cli 122 | 123 | ## Uninstallation 124 | 125 | ### Automatic 126 | 127 | Using curl (Mac OS X & Windows): 128 | ``` 129 | curl https://raw.githubusercontent.com/alberthier/git-webui/master/install/uninstaller.sh | bash 130 | ``` 131 | 132 | Using wget (Linux): 133 | ``` 134 | wget -O - https://raw.githubusercontent.com/alberthier/git-webui/master/install/uninstaller.sh | bash 135 | ``` 136 | 137 | ### Manual 138 | 139 | ``` 140 | rm -rf 141 | git config --global --unset-all alias.webui 142 | git config --global --remove-section webui 143 | ``` 144 | 145 | ## Contributing 146 | 147 | You can clone the source code of [git-webui on GitHub](https://github.com/alberthier/git-webui) 148 | 149 | After executing `grunt` git-webui is available in the `dist` folder. 150 | 151 | Please don't commit any content to the `release` folder. This is for end-users release versions, not for work-in-progress versions 152 | 153 | ## Packaging 154 | 155 | If you want to build a DEB, RPM or Homebrew package for git-webui, you only need the content of the `release` folder. 156 | 157 | Installing git-webui globally on the system is nothing else than 158 | ``` 159 | cp -rf release/* /usr 160 | ``` 161 | 162 | ## Limitations 163 | 164 | - If you have no web browser installed at all (headless server), you should start git webui with the `--no-browser` option. Otherwise git-webui may freeze searching for a browser. 165 | 166 | ## Author 167 | 168 | [Éric ALBER](mailto:eric.alber@gmail.com) ([@eric_alber](https://twitter.com/eric_alber)) 169 | 170 | ## License 171 | 172 | This software is licensed under the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license 173 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-webui", 3 | "version": "1.3.0", 4 | "authors": [ 5 | "Éric ALBER " 6 | ], 7 | "description": "A web user interface for git", 8 | "keywords": [ 9 | "git", 10 | "gui" 11 | ], 12 | "license": "LGPL", 13 | "ignore": [ 14 | "**/.*", 15 | "node_modules", 16 | "bower_components", 17 | "test", 18 | "tests" 19 | ], 20 | "devDependencies": { 21 | "bootstrap": "~3.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /install/installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2015 Eric ALBER 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | if [ "$OS" = "Windows_NT" ]; then 18 | # We are on windows, check if Python is installed 19 | python -V > /dev/null 2>&1 20 | if [ $? -eq 0 ]; then 21 | PYTHON=python 22 | else 23 | reg query "HKLM\SOFTWARE\Python\PythonCore" > /dev/null 2>&1 24 | if [ $? -ne 0 ]; then 25 | echo "Please install Python first" 26 | echo "You can download it from http://python.org/downloads/" 27 | exit 1 28 | fi 29 | PYTHON_REG_PATH=`reg query "HKLM\SOFTWARE\Python\PythonCore" | grep HKEY | sort | tail -n 1` 30 | PYTHON_ROOT=/`reg query "${PYTHON_REG_PATH}\InstallPath" -ve | grep REG_SZ | sed -e "s/.*REG_SZ\s\+\(.*\)/\1/" | sed -e "s/://" | sed -e "s/\\\\\/\//g"` 31 | PYTHON=${PYTHON_ROOT}python.exe 32 | fi 33 | fi 34 | 35 | cd $HOME 36 | rm -rf .git-webui > /dev/null 2>&1 37 | echo "Cloning git-webui repository" 38 | git clone --depth 1 https://github.com/alberthier/git-webui.git .git-webui 39 | echo "Enabling auto update" 40 | git config --global --replace-all webui.autoupdate true 41 | echo "Installing 'webui' alias" 42 | if [ "$OS" = "Windows_NT" ]; then 43 | git config --global --replace-all alias.webui "!${PYTHON} $HOME/.git-webui/release/libexec/git-core/git-webui" 44 | else 45 | git config --global --replace-all alias.webui !$HOME/.git-webui/release/libexec/git-core/git-webui 46 | fi 47 | -------------------------------------------------------------------------------- /install/uninstaller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2015 Eric ALBER 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | cd $HOME 18 | echo "Removing \$HOME/.git-webui" 19 | rm -rf .git-webui > /dev/null 2>&1 20 | echo "Cleaning up global gitconfig file" 21 | git config --global --unset-all alias.webui 22 | git config --global --remove-section webui 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-webui", 3 | "version": "1.3.0", 4 | "description": "A web user interface for git", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:alberthier/git-webui.git" 12 | }, 13 | "keywords": [ 14 | "git", 15 | "gui" 16 | ], 17 | "author": "Éric ALBER ", 18 | "license": "LGPL", 19 | "devDependencies": { 20 | "bower": "^1.3.7", 21 | "grunt": "^0.4.5", 22 | "grunt-contrib-clean": "^0.5.0", 23 | "grunt-contrib-copy": "^0.5.0", 24 | "grunt-contrib-less": "^0.11.3", 25 | "grunt-contrib-watch": "^0.6.1", 26 | "grunt-shell": "^0.7.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /release/libexec/git-core/git-webui: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2015 Eric ALBER 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License") 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import argparse 18 | import codecs 19 | import datetime 20 | import os 21 | import platform 22 | import posixpath 23 | import shlex 24 | import socket 25 | import subprocess 26 | import sys 27 | import webbrowser 28 | 29 | if sys.version > '3': 30 | from http.server import SimpleHTTPRequestHandler, HTTPServer 31 | from urllib.parse import unquote, urlparse 32 | else: 33 | from SimpleHTTPServer import SimpleHTTPRequestHandler 34 | from BaseHTTPServer import HTTPServer 35 | from urllib import unquote 36 | from urlparse import urlparse 37 | 38 | 39 | 40 | 41 | IS_WINDOWS = platform.system() == "Windows" 42 | 43 | allowed_hosts = ["127.0.0.1", "localhost"] 44 | 45 | 46 | 47 | 48 | class WebUiHttpServer(HTTPServer): 49 | def __init__(self, server_address, RequestHandlerClass, bind_and_activate = True): 50 | if not isinstance(server_address, tuple) or len(server_address) < 2: 51 | raise TypeError("server_address must be a tuple (address,port)") 52 | host = server_address[0] 53 | port = server_address[1] 54 | if host is None or host == "": 55 | HTTPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate) 56 | else: 57 | address_info = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)[0] 58 | self.address_family = address_info[0] 59 | HTTPServer.__init__(self, address_info[4], RequestHandlerClass, bind_and_activate) 60 | 61 | 62 | 63 | 64 | class WebUiRequestHandler(SimpleHTTPRequestHandler): 65 | 66 | WEB_ROOT = None 67 | REPO_ROOT = None 68 | 69 | @classmethod 70 | def initialize(cls, repo_root): 71 | web_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))) 72 | web_root = os.path.join(web_root, "share", "git-webui", "webui") 73 | WebUiRequestHandler.WEB_ROOT = web_root 74 | WebUiRequestHandler.REPO_ROOT = repo_root 75 | 76 | 77 | def translate_path(self, path): 78 | if self.is_git_request(): 79 | return path 80 | 81 | # abandon query parameters 82 | path = path.split('?',1)[0] 83 | path = path.split('#',1)[0] 84 | # Don't forget explicit trailing slash when normalizing. Issue17324 85 | trailing_slash = True if path.rstrip().endswith('/') else False 86 | path = posixpath.normpath(unquote(path)) 87 | words = path.split('/') 88 | words = filter(None, words) 89 | path = WebUiRequestHandler.WEB_ROOT 90 | for word in words: 91 | drive, word = os.path.splitdrive(word) 92 | head, word = os.path.split(word) 93 | if word in (os.curdir, os.pardir): continue 94 | path = os.path.join(path, word) 95 | if trailing_slash: 96 | path += '/' 97 | return path 98 | 99 | 100 | def do_GET(self): 101 | if self.path.startswith("/git/cat-file/"): 102 | obj = self.path[14:] 103 | self.process(["git", "-c", "color.ui=false", "cat-file", "-p", obj], b"", True, False) 104 | elif self.path == "/dirname": 105 | wc = os.path.split(WebUiRequestHandler.REPO_ROOT)[1] 106 | self.send_text(200, codecs.encode(wc, "utf-8")) 107 | elif self.path == "/hostname": 108 | self.send_text(200, codecs.encode(socket.gethostname(), "utf-8")) 109 | elif self.path == "/viewonly": 110 | vo = "1" if self.is_view_only() else "0" 111 | self.send_text(200, codecs.encode(vo, "utf-8")) 112 | elif self.is_git_request(): 113 | self.process_http_backend() 114 | else: 115 | SimpleHTTPRequestHandler.do_GET(self) 116 | 117 | 118 | def do_POST(self): 119 | if self.path == "/git": 120 | content_length = int(self.headers["Content-Length"]) 121 | content = self.rfile.read(content_length) 122 | # Convention : First line = git arguments, rest = git process stdin 123 | i = content.find(b'\n') 124 | if i != -1: 125 | args = content[:i] 126 | stdin = content[i + 1:] 127 | else: 128 | args = content 129 | stdin = b"" 130 | if sys.version > '3': 131 | args = codecs.decode(args, "utf-8") 132 | cmd = shlex.split("git -c color.ui=true " + args) 133 | action = cmd[3] 134 | if not self.is_view_only() or args in ["branch", "branch --remotes", "tag"] or action in ["show", "status", "log", "ls-tree"]: 135 | self.process(cmd, stdin, True, True) 136 | else: 137 | self.send_error(403) 138 | elif self.is_git_request(): 139 | self.process_http_backend() 140 | else: 141 | SimpleHTTPRequestHandler.do_POST(self) 142 | 143 | 144 | def is_view_only(self): 145 | host = self.headers.get("Host", "").split(":")[0] 146 | return host not in allowed_hosts 147 | 148 | 149 | def process_http_backend(self): 150 | parsed_path = urlparse(self.path) 151 | 152 | env = {} 153 | env["GIT_PROJECT_ROOT"] = WebUiRequestHandler.REPO_ROOT + "/.git" 154 | env["GIT_HTTP_EXPORT_ALL"] = "" 155 | env["REQUEST_METHOD"] = self.command 156 | env["REMOTE_ADDR"] = self.client_address[0] 157 | env["PATH_INFO"] = parsed_path.path 158 | env["QUERY_STRING"] = parsed_path.query 159 | if 'Content-Type' in self.headers: 160 | env['CONTENT_TYPE'] = self.headers['Content-Type'] 161 | if 'Content-Length' in self.headers: 162 | contentLength = self.headers['Content-Length'] 163 | env['CONTENT_LENGTH'] = contentLength 164 | inputData = self.rfile.read(int(contentLength)) 165 | else: 166 | env['CONTENT_LENGTH'] = '0' 167 | inputData = b"" 168 | 169 | self.wfile.write(b"HTTP/1.0 200 OK\r\n") 170 | self.wfile.flush() 171 | self.process(['git', "-c", "color.ui=false", 'http-backend'], inputData, False, False, env) 172 | 173 | 174 | def is_git_request(self): 175 | return self.headers.get("User-Agent", "").startswith("git/") 176 | 177 | 178 | def process(self, cmd, stdin, add_headers, add_footers, env = None): 179 | if add_headers: 180 | self.send_response(200) 181 | self.end_headers() 182 | # Convention : send first all git output, then stderr. 183 | # Finially we add footers: a blank line followed by key / value pairs as in HTTP headers 184 | if IS_WINDOWS: 185 | # On windows we cannot pipe the process output directly to the socket. 186 | git = subprocess.Popen(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, cwd = WebUiRequestHandler.REPO_ROOT, env = env) 187 | git.stdin.write(stdin) 188 | git.stdin.close() 189 | bufferlen = 64 * 1024 190 | while True: 191 | data = git.stdout.read(bufferlen) 192 | self.wfile.write(data) 193 | if len(data) < bufferlen: 194 | break 195 | stderr = git.stderr.read() 196 | git.wait() 197 | else: 198 | git = subprocess.Popen(cmd, stdin = subprocess.PIPE, stdout = self.wfile, stderr = subprocess.PIPE, cwd = WebUiRequestHandler.REPO_ROOT, env = env) 199 | stdout, stderr = git.communicate(stdin) 200 | if add_footers: 201 | self.wfile.write(stderr) 202 | self.wfile.write(b"\r\n") 203 | self.wfile.write(codecs.encode("\r\nGit-Stderr-Length: " + str(len(stderr)), "utf-8")) 204 | self.wfile.write(codecs.encode("\r\nGit-Return-Code: " + str(git.returncode), "utf-8")) 205 | elif git.returncode != 0: 206 | print(stderr) 207 | 208 | 209 | def send_text(self, http_status, text): 210 | self.send_response(http_status) 211 | self.send_header("Content-Type", "text/plain") 212 | self.send_header("Content-Length", len(text)) 213 | self.end_headers() 214 | self.wfile.write(text) 215 | 216 | 217 | 218 | 219 | def auto_update(): 220 | repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))) 221 | dot_git = os.path.join(repo_root, ".git") 222 | if not os.path.exists(dot_git): 223 | return 224 | fetch_head = os.path.join(dot_git, "FETCH_HEAD") 225 | if not os.path.exists(fetch_head): 226 | needs_update = True 227 | else: 228 | fetch_head_mtime = os.stat(fetch_head).st_mtime 229 | needs_update = datetime.datetime.now() - datetime.datetime.fromtimestamp(fetch_head_mtime) > datetime.timedelta(14) 230 | if needs_update: 231 | # Check if update is allowed 232 | allow_autoupdate = read_config_boolean("autoupdate") 233 | if not allow_autoupdate: 234 | return 235 | 236 | print("Checking for update...") 237 | # Update the repository 238 | subprocess.call(["git", "-c", "color.ui=false", "pull"], cwd = repo_root) 239 | # Replace the current process with the updated one 240 | os.execl(sys.executable, os.path.split(sys.executable)[1], *sys.argv) 241 | 242 | 243 | 244 | 245 | def read_config_string(name, default=None): 246 | git = subprocess.Popen(["git", "-c", "color.ui=false", "config", "--get", "webui.{}".format(name)], stdout = subprocess.PIPE, stderr = subprocess.PIPE) 247 | out = codecs.decode(git.communicate()[0], "utf-8") 248 | if git.returncode != 0: 249 | # The key doesn't exist 250 | return default 251 | return out.strip() 252 | 253 | 254 | 255 | 256 | def read_config_boolean(name, default=False): 257 | r = read_config_string(name) 258 | if r == None: 259 | return bool(default) 260 | return str(r).lower() == "true" 261 | 262 | 263 | 264 | 265 | def get_setting_string(args, name, default=None): 266 | if vars(args)[name] is not None: 267 | return vars(args)[name] 268 | return read_config_string(name, default) 269 | 270 | 271 | 272 | 273 | def get_setting_boolean(args, name, default=False): 274 | if vars(args)[name] is not None: 275 | return vars(args)[name] 276 | return read_config_boolean(name, default) 277 | 278 | 279 | 280 | 281 | if __name__ == '__main__': 282 | auto_update() 283 | parser = argparse.ArgumentParser(description = "Simple HTTP server for git webui") 284 | parser.add_argument("--port", type = int, help = "server port") 285 | parser.add_argument("--repo-root", help = "repository root path. By default goes up a dir until a '.git' directory is found") 286 | parser.add_argument("--allow-hosts", help = "what other host(s) are allowed to have write access") 287 | parser.add_argument("--no-browser", dest = "nobrowser", action = 'store_const', const = True, help = "do not start web browser") 288 | parser.add_argument("--host", help = "the host webui listens on (default is all)") 289 | 290 | args = parser.parse_args() 291 | 292 | if args.repo_root is None: 293 | args.repo_root = os.path.abspath(os.getcwd()) 294 | while '.git' not in os.listdir(args.repo_root): 295 | new_root = os.path.dirname(args.repo_root) 296 | if new_root == args.repo_root: 297 | args.repo_root = None 298 | break 299 | else: 300 | args.repo_root = new_root 301 | 302 | writeaccess = read_config_string("writeaccess") 303 | if writeaccess is not None: 304 | allowed_hosts = writeaccess.split(',') if writeaccess else [] 305 | if args.allow_hosts is not None: 306 | allowed_hosts += args.allow_hosts.split(',') 307 | 308 | if args.repo_root is None or '.git' not in os.listdir(args.repo_root): 309 | sys.stderr.write("No git repository found\n") 310 | sys.exit(1) 311 | WebUiRequestHandler.initialize(args.repo_root) 312 | 313 | args.port = get_setting_string(args, 'port', None) 314 | port = int(args.port) if args.port is not None else 8000 315 | host = get_setting_string(args, 'host', "") 316 | httpd = None 317 | while httpd is None: 318 | try: 319 | httpd = WebUiHttpServer((host, port), WebUiRequestHandler) 320 | except socket.error as e: 321 | if args.port is not None: 322 | sys.stderr.write("Port {} is already in use, try another one\n".format(port)) 323 | sys.exit(2) 324 | else: 325 | sys.stderr.write("Port {} is already in use, trying another one\n".format(port)) 326 | port += 1 327 | 328 | url = "http://{}:{}".format(host if host else "localhost", port) 329 | print("Serving at {}".format(url)) 330 | nobrowser = get_setting_boolean(args, "nobrowser") 331 | if not nobrowser: 332 | webbrowser.open(url) 333 | 334 | try: 335 | httpd.serve_forever() 336 | except KeyboardInterrupt: 337 | httpd.server_close() 338 | sys.exit(0) 339 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/css/git-webui.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Eric ALBER 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | html, 17 | body { 18 | height: 100%; 19 | width: 100%; 20 | } 21 | body { 22 | display: flex; 23 | display: -webkit-flex; 24 | min-height: 0; 25 | min-width: 0; 26 | flex-direction: column; 27 | -webkit-flex-direction: column; 28 | margin: 0; 29 | font: 10pt sans-serif; 30 | } 31 | #error-modal .modal-body { 32 | margin-bottom: 0; 33 | } 34 | #help-modal img { 35 | display: block; 36 | margin: 0 auto; 37 | border: 1px solid #dddddd; 38 | } 39 | #message-box .alert { 40 | margin: 0; 41 | } 42 | #global-container { 43 | display: flex; 44 | display: -webkit-flex; 45 | min-height: 0; 46 | min-width: 0; 47 | flex: 1 1 0; 48 | -webkit-flex: 1 1 0; 49 | } 50 | #sidebar { 51 | display: flex; 52 | display: -webkit-flex; 53 | min-height: 0; 54 | min-width: 0; 55 | flex-direction: column; 56 | -webkit-flex-direction: column; 57 | width: 14em; 58 | background-color: #333333; 59 | } 60 | #sidebar #sidebar-logo { 61 | display: block; 62 | margin: 8px auto 8px auto; 63 | height: 2em; 64 | text-align: center; 65 | } 66 | #sidebar .branch-current { 67 | font-weight: bold; 68 | } 69 | #sidebar #sidebar-content { 70 | flex: 1 1 0; 71 | -webkit-flex: 1 1 0; 72 | overflow-x: hidden; 73 | overflow-y: auto; 74 | cursor: default; 75 | border-top: 1px solid #1a1a1a; 76 | color: #eeeeee; 77 | } 78 | #sidebar #sidebar-content > :first-child { 79 | border-top: 1px solid #5e5e5e; 80 | } 81 | #sidebar #sidebar-content h4:before { 82 | vertical-align: middle; 83 | margin: 12px; 84 | } 85 | #sidebar #sidebar-content h4 { 86 | vertical-align: baseline; 87 | margin: 0.5em 0 0 0; 88 | } 89 | #sidebar #sidebar-content > section h4 { 90 | font-size: 125%; 91 | cursor: pointer; 92 | padding: 10px 0; 93 | } 94 | #sidebar #sidebar-content #sidebar-workspace h4:before { 95 | content: url(/img/computer.svg); 96 | } 97 | #sidebar #sidebar-content #sidebar-remote h4:before { 98 | content: url(/img/daemon.svg); 99 | } 100 | #sidebar #sidebar-content #sidebar-local-branches h4:before, 101 | #sidebar #sidebar-content #sidebar-remote-branches h4:before { 102 | content: url(/img/branch.svg); 103 | } 104 | #sidebar #sidebar-content #sidebar-tags h4:before { 105 | content: url(/img/tag.svg); 106 | } 107 | #sidebar #sidebar-content ul { 108 | margin: 0; 109 | padding: 0; 110 | } 111 | #sidebar #sidebar-content li:before { 112 | content: "\2022"; 113 | margin: 12px; 114 | } 115 | #sidebar #sidebar-content li { 116 | cursor: pointer; 117 | padding: 6px 0 6px 1em; 118 | white-space: nowrap; 119 | overflow: hidden; 120 | text-overflow: ellipsis; 121 | } 122 | #sidebar #sidebar-content .active { 123 | background-color: #428bca; 124 | } 125 | #sidebar .modal-dialog .list-group-item { 126 | border: 0; 127 | } 128 | #main-view { 129 | display: flex; 130 | display: -webkit-flex; 131 | min-height: 0; 132 | min-width: 0; 133 | flex: 1 1 0; 134 | -webkit-flex: 1 1 0; 135 | } 136 | #main-view .jumbotron { 137 | flex: 1 1 0; 138 | -webkit-flex: 1 1 0; 139 | margin: 0; 140 | } 141 | #main-view .jumbotron h1 { 142 | font-size: 36pt; 143 | margin-top: 0; 144 | } 145 | #history-view { 146 | display: flex; 147 | display: -webkit-flex; 148 | min-height: 0; 149 | min-width: 0; 150 | flex: 1 1 0; 151 | -webkit-flex: 1 1 0; 152 | } 153 | #log-view { 154 | flex: 1 1 0; 155 | -webkit-flex: 1 1 0; 156 | overflow-y: auto; 157 | cursor: default; 158 | margin-bottom: 0; 159 | border-right: 1px #dddddd solid; 160 | position: relative; 161 | } 162 | #log-view svg { 163 | position: absolute; 164 | top: 0; 165 | left: 0; 166 | } 167 | #log-view svg path { 168 | stroke-width: 2; 169 | fill: none; 170 | } 171 | #log-view svg circle { 172 | stroke: none; 173 | fill: #555555; 174 | } 175 | #log-view .log-entry { 176 | padding: 5px 10px; 177 | background-color: transparent; 178 | margin: 0; 179 | border-top-width: 0; 180 | } 181 | #log-view .log-entry:hover { 182 | background-color: rgba(244, 244, 244, 0.9); 183 | } 184 | #log-view .log-entry.active { 185 | background-color: rgba(46, 127, 197, 0.9); 186 | } 187 | #log-view .log-entry.active header h6 a { 188 | color: #ffffff; 189 | } 190 | #log-view .log-entry header { 191 | display: flex; 192 | display: -webkit-flex; 193 | min-height: 0; 194 | min-width: 0; 195 | align-items: baseline; 196 | -webkit-align-items: baseline; 197 | } 198 | #log-view .log-entry header h6 { 199 | font-weight: bold; 200 | margin-top: 0; 201 | } 202 | #log-view .log-entry header h6 a { 203 | color: #777777; 204 | } 205 | #log-view .log-entry header .badge { 206 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 207 | } 208 | #log-view .log-entry header .log-entry-date { 209 | margin-left: auto; 210 | } 211 | #log-view .log-entry .list-group-item-text { 212 | white-space: nowrap; 213 | overflow: hidden; 214 | text-overflow: ellipsis; 215 | } 216 | #log-view .log-entry-more { 217 | padding: 10px 16px; 218 | } 219 | #log-view .log-entry-more a { 220 | display: block; 221 | text-align: center; 222 | } 223 | #commit-view { 224 | display: flex; 225 | display: -webkit-flex; 226 | min-height: 0; 227 | min-width: 0; 228 | flex-direction: column; 229 | -webkit-flex-direction: column; 230 | flex: 1 1 0; 231 | -webkit-flex: 1 1 0; 232 | } 233 | #commit-view #commit-view-header { 234 | border: solid #dddddd; 235 | border-width: 0 0 1px 0; 236 | } 237 | #commit-view #commit-view-content { 238 | display: flex; 239 | display: -webkit-flex; 240 | min-height: 0; 241 | min-width: 0; 242 | flex: 1 1 0; 243 | -webkit-flex: 1 1 0; 244 | flex-direction: column; 245 | -webkit-flex-direction: column; 246 | } 247 | .file-list-view { 248 | -webkit-touch-callout: none; 249 | -webkit-user-select: none; 250 | -khtml-user-select: none; 251 | -moz-user-select: none; 252 | -ms-user-select: none; 253 | user-select: none; 254 | flex: 1 1 0; 255 | -webkit-flex: 1 1 0; 256 | } 257 | .file-list-view .file-list-container { 258 | display: flex; 259 | display: -webkit-flex; 260 | min-height: 0; 261 | min-width: 0; 262 | flex: 1 1 0; 263 | -webkit-flex: 1 1 0; 264 | overflow: auto; 265 | } 266 | .file-list-view .file-list-container .file-list-left-container { 267 | border: solid #dddddd; 268 | border-width: 0 1px 0 0; 269 | } 270 | .file-list-view .file-list-container .file-list-right-container, 271 | .file-list-view .file-list-container .file-list-left-container { 272 | flex: 1 1 0; 273 | -webkit-flex: 1 1 0; 274 | overflow: auto; 275 | } 276 | .file-list-view .file-list-container .list-group { 277 | display: table; 278 | margin: 0; 279 | width: 100%; 280 | } 281 | .file-list-view .file-list-container .list-group .list-group-item:first-child { 282 | border-top: 0; 283 | } 284 | .file-list-view .file-list-container .list-group .list-group-item { 285 | padding: 5px 10px; 286 | border-left: 0; 287 | border-right: 0; 288 | white-space: nowrap; 289 | } 290 | .diff-view-container { 291 | display: flex; 292 | display: -webkit-flex; 293 | min-height: 0; 294 | min-width: 0; 295 | flex: 1 1 0; 296 | -webkit-flex: 1 1 0; 297 | flex-direction: column; 298 | -webkit-flex-direction: column; 299 | border: 0; 300 | margin: 0; 301 | } 302 | .diff-view-container .panel-heading { 303 | margin: 0; 304 | padding: 5px 16px; 305 | display: flex; 306 | display: -webkit-flex; 307 | min-height: 0; 308 | min-width: 0; 309 | } 310 | .diff-view-container .panel-heading .btn-group { 311 | display: flex; 312 | display: -webkit-flex; 313 | min-height: 0; 314 | min-width: 0; 315 | align-items: baseline; 316 | -webkit-align-items: baseline; 317 | } 318 | .diff-view-container .panel-heading .btn-group span { 319 | padding-left: 5px; 320 | } 321 | .diff-view-container .panel-heading .diff-selection-buttons { 322 | display: flex; 323 | display: -webkit-flex; 324 | min-height: 0; 325 | min-width: 0; 326 | flex: 1 1 0; 327 | -webkit-flex: 1 1 0; 328 | align-items: flex-end; 329 | justify-content: flex-end; 330 | } 331 | .diff-view-container .panel-body { 332 | display: flex; 333 | display: -webkit-flex; 334 | min-height: 0; 335 | min-width: 0; 336 | flex: 1 1 0; 337 | -webkit-flex: 1 1 0; 338 | padding: 0; 339 | } 340 | .diff-view-container .panel-body:before { 341 | display: block; 342 | } 343 | .diff-view-container .panel-body .diff-view { 344 | flex: 1 1 0; 345 | -webkit-flex: 1 1 0; 346 | overflow: auto; 347 | } 348 | .diff-view-container .panel-body .diff-view:nth-child(2) { 349 | border: solid #dddddd; 350 | border-width: 0 0 0 1px; 351 | } 352 | .diff-view-container .panel-body .diff-view .diff-view-lines { 353 | display: table; 354 | width: 100%; 355 | } 356 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-view-line { 357 | margin: 0; 358 | padding: 0 0.3em 0 0.3em; 359 | background-color: white; 360 | border: 0; 361 | } 362 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-view-line.active { 363 | background-color: #428bca; 364 | color: white; 365 | } 366 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-header { 367 | font-weight: bold; 368 | background-color: #eeeeee; 369 | } 370 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-phantom { 371 | background-color: #eeeeee; 372 | } 373 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-header.diff-line-add { 374 | background-color: #eeeeee; 375 | color: #669900; 376 | } 377 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-header.diff-line-del { 378 | background-color: #eeeeee; 379 | color: #cc0000; 380 | } 381 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-header.diff-line-offset { 382 | background-color: #eeeeee; 383 | color: #9933cc; 384 | } 385 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-add { 386 | background-color: #c5e26d; 387 | } 388 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-del { 389 | background-color: #ff9494; 390 | } 391 | .diff-view-container .panel-body .diff-view .diff-view-lines .diff-line-offset { 392 | background-color: #d6adeb; 393 | } 394 | #tree-view { 395 | flex: 1 1 0; 396 | -webkit-flex: 1 1 0; 397 | flex-direction: column; 398 | -webkit-flex-direction: column; 399 | display: flex; 400 | display: -webkit-flex; 401 | min-height: 0; 402 | min-width: 0; 403 | } 404 | #tree-view .breadcrumb { 405 | padding: 10px 16px; 406 | margin-bottom: 0; 407 | border-bottom: 1px #dddddd solid; 408 | } 409 | #tree-view #tree-view-tree-content { 410 | overflow: auto; 411 | flex: 1 1 0; 412 | -webkit-flex: 1 1 0; 413 | margin: 0; 414 | } 415 | #tree-view #tree-view-blob-content { 416 | display: flex; 417 | display: -webkit-flex; 418 | min-height: 0; 419 | min-width: 0; 420 | flex-direction: column; 421 | -webkit-flex-direction: column; 422 | flex: 1 1 0; 423 | -webkit-flex: 1 1 0; 424 | width: 100%; 425 | } 426 | #tree-view #tree-view-blob-content iframe { 427 | flex: 1 1 0; 428 | -webkit-flex: 1 1 0; 429 | border: 0; 430 | } 431 | #tree-view .list-group-item { 432 | display: flex; 433 | display: -webkit-flex; 434 | min-height: 0; 435 | min-width: 0; 436 | cursor: pointer; 437 | border: 0; 438 | padding: 5px 10px; 439 | } 440 | #tree-view .list-group-item > :first-child { 441 | flex: 1 1 0; 442 | -webkit-flex: 1 1 0; 443 | } 444 | #tree-view .list-group-item > :last-child { 445 | width: 2em; 446 | } 447 | #tree-view .tree-item-tree:before { 448 | content: url(/img/folder.svg); 449 | vertical-align: middle; 450 | margin: 0.3em; 451 | } 452 | #tree-view .tree-item-blob:before { 453 | content: url(/img/file.svg); 454 | vertical-align: middle; 455 | margin: 0.3em; 456 | } 457 | #tree-view .tree-item-symlink { 458 | font-style: italic; 459 | } 460 | #workspace-view, 461 | #commit-explorer-view { 462 | display: flex; 463 | display: -webkit-flex; 464 | min-height: 0; 465 | min-width: 0; 466 | flex-direction: column; 467 | -webkit-flex-direction: column; 468 | flex: 1 1 0; 469 | -webkit-flex: 1 1 0; 470 | } 471 | #workspace-view #workspace-diff-view, 472 | #commit-explorer-view #workspace-diff-view, 473 | #workspace-view #commit-explorer-diff-view, 474 | #commit-explorer-view #commit-explorer-diff-view { 475 | display: flex; 476 | display: -webkit-flex; 477 | min-height: 0; 478 | min-width: 0; 479 | flex: 2 2 0; 480 | -webkit-flex: 2 2 0; 481 | overflow: auto; 482 | } 483 | #workspace-view #workspace-editor, 484 | #commit-explorer-view #workspace-editor, 485 | #workspace-view #commit-explorer-navigator-view, 486 | #commit-explorer-view #commit-explorer-navigator-view { 487 | flex: 1 1 0; 488 | -webkit-flex: 1 1 0; 489 | display: flex; 490 | display: -webkit-flex; 491 | min-height: 0; 492 | min-width: 0; 493 | align-items: stretch; 494 | -webkit-align-items: stretch; 495 | border: solid #dddddd; 496 | border-width: 1px 0 0 0; 497 | cursor: default; 498 | } 499 | #workspace-view #workspace-editor #working-copy-view, 500 | #commit-explorer-view #workspace-editor #working-copy-view, 501 | #workspace-view #commit-explorer-navigator-view #working-copy-view, 502 | #commit-explorer-view #commit-explorer-navigator-view #working-copy-view, 503 | #workspace-view #workspace-editor #staging-area-view, 504 | #commit-explorer-view #workspace-editor #staging-area-view, 505 | #workspace-view #commit-explorer-navigator-view #staging-area-view, 506 | #commit-explorer-view #commit-explorer-navigator-view #staging-area-view { 507 | -webkit-touch-callout: none; 508 | -webkit-user-select: none; 509 | -khtml-user-select: none; 510 | -moz-user-select: none; 511 | -ms-user-select: none; 512 | user-select: none; 513 | flex: 2 2 0; 514 | -webkit-flex: 2 2 0; 515 | } 516 | #workspace-view #workspace-editor #working-copy-view .file-list-container, 517 | #commit-explorer-view #workspace-editor #working-copy-view .file-list-container, 518 | #workspace-view #commit-explorer-navigator-view #working-copy-view .file-list-container, 519 | #commit-explorer-view #commit-explorer-navigator-view #working-copy-view .file-list-container, 520 | #workspace-view #workspace-editor #staging-area-view .file-list-container, 521 | #commit-explorer-view #workspace-editor #staging-area-view .file-list-container, 522 | #workspace-view #commit-explorer-navigator-view #staging-area-view .file-list-container, 523 | #commit-explorer-view #commit-explorer-navigator-view #staging-area-view .file-list-container { 524 | flex: 1 1 0; 525 | -webkit-flex: 1 1 0; 526 | overflow: auto; 527 | } 528 | #workspace-view #workspace-editor #working-copy-view .file-list-container .list-group, 529 | #commit-explorer-view #workspace-editor #working-copy-view .file-list-container .list-group, 530 | #workspace-view #commit-explorer-navigator-view #working-copy-view .file-list-container .list-group, 531 | #commit-explorer-view #commit-explorer-navigator-view #working-copy-view .file-list-container .list-group, 532 | #workspace-view #workspace-editor #staging-area-view .file-list-container .list-group, 533 | #commit-explorer-view #workspace-editor #staging-area-view .file-list-container .list-group, 534 | #workspace-view #commit-explorer-navigator-view #staging-area-view .file-list-container .list-group, 535 | #commit-explorer-view #commit-explorer-navigator-view #staging-area-view .file-list-container .list-group { 536 | display: table; 537 | margin: 0; 538 | width: 100%; 539 | } 540 | #workspace-view #workspace-editor #working-copy-view .file-list-container .list-group .list-group-item:first-child, 541 | #commit-explorer-view #workspace-editor #working-copy-view .file-list-container .list-group .list-group-item:first-child, 542 | #workspace-view #commit-explorer-navigator-view #working-copy-view .file-list-container .list-group .list-group-item:first-child, 543 | #commit-explorer-view #commit-explorer-navigator-view #working-copy-view .file-list-container .list-group .list-group-item:first-child, 544 | #workspace-view #workspace-editor #staging-area-view .file-list-container .list-group .list-group-item:first-child, 545 | #commit-explorer-view #workspace-editor #staging-area-view .file-list-container .list-group .list-group-item:first-child, 546 | #workspace-view #commit-explorer-navigator-view #staging-area-view .file-list-container .list-group .list-group-item:first-child, 547 | #commit-explorer-view #commit-explorer-navigator-view #staging-area-view .file-list-container .list-group .list-group-item:first-child { 548 | border-top: 0; 549 | } 550 | #workspace-view #workspace-editor #working-copy-view .file-list-container .list-group .list-group-item, 551 | #commit-explorer-view #workspace-editor #working-copy-view .file-list-container .list-group .list-group-item, 552 | #workspace-view #commit-explorer-navigator-view #working-copy-view .file-list-container .list-group .list-group-item, 553 | #commit-explorer-view #commit-explorer-navigator-view #working-copy-view .file-list-container .list-group .list-group-item, 554 | #workspace-view #workspace-editor #staging-area-view .file-list-container .list-group .list-group-item, 555 | #commit-explorer-view #workspace-editor #staging-area-view .file-list-container .list-group .list-group-item, 556 | #workspace-view #commit-explorer-navigator-view #staging-area-view .file-list-container .list-group .list-group-item, 557 | #commit-explorer-view #commit-explorer-navigator-view #staging-area-view .file-list-container .list-group .list-group-item { 558 | padding: 5px 10px; 559 | border-left: 0; 560 | border-right: 0; 561 | white-space: nowrap; 562 | } 563 | #workspace-view #workspace-editor #commit-message-view, 564 | #commit-explorer-view #workspace-editor #commit-message-view, 565 | #workspace-view #commit-explorer-navigator-view #commit-message-view, 566 | #commit-explorer-view #commit-explorer-navigator-view #commit-message-view { 567 | flex: 3 3 0; 568 | -webkit-flex: 3 3 0; 569 | } 570 | #workspace-view #workspace-editor #commit-message-view textarea, 571 | #commit-explorer-view #workspace-editor #commit-message-view textarea, 572 | #workspace-view #commit-explorer-navigator-view #commit-message-view textarea, 573 | #commit-explorer-view #commit-explorer-navigator-view #commit-message-view textarea { 574 | flex: 1 1 0; 575 | -webkit-flex: 1 1 0; 576 | resize: none; 577 | margin: 0; 578 | border: 0; 579 | outline: none; 580 | padding: 0.3em; 581 | } 582 | #workspace-view #workspace-editor .panel, 583 | #commit-explorer-view #workspace-editor .panel, 584 | #workspace-view #commit-explorer-navigator-view .panel, 585 | #commit-explorer-view #commit-explorer-navigator-view .panel { 586 | flex: 1 1 0; 587 | -webkit-flex: 1 1 0; 588 | display: flex; 589 | display: -webkit-flex; 590 | min-height: 0; 591 | min-width: 0; 592 | flex-direction: column; 593 | -webkit-flex-direction: column; 594 | margin: 0; 595 | border-width: 0 1px 0 0; 596 | border-style: solid; 597 | border-color: #dddddd; 598 | } 599 | #workspace-view #workspace-editor .panel .panel-heading, 600 | #commit-explorer-view #workspace-editor .panel .panel-heading, 601 | #workspace-view #commit-explorer-navigator-view .panel .panel-heading, 602 | #commit-explorer-view #commit-explorer-navigator-view .panel .panel-heading { 603 | -webkit-touch-callout: none; 604 | -webkit-user-select: none; 605 | -khtml-user-select: none; 606 | -moz-user-select: none; 607 | -ms-user-select: none; 608 | user-select: none; 609 | padding: 5px 10px; 610 | display: flex; 611 | display: -webkit-flex; 612 | min-height: 0; 613 | min-width: 0; 614 | align-items: center; 615 | -webkit-align-items: center; 616 | } 617 | #workspace-view #workspace-editor .panel .panel-heading h5, 618 | #commit-explorer-view #workspace-editor .panel .panel-heading h5, 619 | #workspace-view #commit-explorer-navigator-view .panel .panel-heading h5, 620 | #commit-explorer-view #commit-explorer-navigator-view .panel .panel-heading h5 { 621 | flex: 1 1 0; 622 | -webkit-flex: 1 1 0; 623 | margin: 0; 624 | } 625 | #workspace-view #workspace-editor .panel:last-child, 626 | #commit-explorer-view #workspace-editor .panel:last-child, 627 | #workspace-view #commit-explorer-navigator-view .panel:last-child, 628 | #commit-explorer-view #commit-explorer-navigator-view .panel:last-child { 629 | border-width: 0; 630 | } 631 | #workspace-view .diff-line-add, 632 | #workspace-view .diff-line-del, 633 | #workspace-view .diff-line-offset { 634 | cursor: pointer; 635 | } 636 | #commit-explorer-view #commit-explorer-navigator-view .panel .panel-body { 637 | flex: 1 1 0; 638 | -webkit-flex: 1 1 0; 639 | overflow: auto; 640 | } 641 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/branch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | Gnome Symbolic Icon Theme 27 | 28 | 29 | 30 | 63 | 74 | 75 | Gnome Symbolic Icon Theme 77 | 79 | 85 | 90 | 95 | 100 | 110 | 120 | 130 | 135 | 136 | 141 | 147 | 153 | 159 | 165 | 166 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/computer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | Gnome Symbolic Icon Theme 30 | 31 | 32 | 33 | 67 | 78 | 79 | Gnome Symbolic Icon Theme 81 | 83 | 89 | 95 | 100 | 106 | 107 | 112 | 118 | 123 | 129 | 135 | 141 | 147 | 148 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/daemon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | Gnome Symbolic Icon Theme 27 | 28 | 29 | 30 | 64 | 75 | 90 | 91 | Gnome Symbolic Icon Theme 93 | 95 | 98 | 105 | 106 | 109 | 116 | 117 | 120 | 127 | 128 | 129 | 135 | 142 | 149 | 156 | 167 | 168 | 174 | 179 | 185 | 190 | 196 | 202 | 208 | 214 | 215 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/doc/log-commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alberthier/git-webui/dee7c192b2ec063cf638c3ec6e99589812b9d231/release/share/git-webui/webui/img/doc/log-commit.png -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/doc/log-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alberthier/git-webui/dee7c192b2ec063cf638c3ec6e99589812b9d231/release/share/git-webui/webui/img/doc/log-tree.png -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/doc/workspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alberthier/git-webui/dee7c192b2ec063cf638c3ec6e99589812b9d231/release/share/git-webui/webui/img/doc/workspace.png -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | Gnome Symbolic Icon Theme 27 | 28 | 29 | 30 | 63 | 72 | 73 | Gnome Symbolic Icon Theme 75 | 77 | 83 | 88 | 93 | 98 | 103 | 104 | 109 | 115 | 121 | 127 | 133 | 134 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | Gnome Symbolic Icon Theme 27 | 28 | 29 | 30 | 60 | 69 | 70 | Gnome Symbolic Icon Theme 72 | 74 | 80 | 85 | 90 | 95 | 100 | 104 | 110 | 111 | 112 | 117 | 123 | 124 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/git-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alberthier/git-webui/dee7c192b2ec063cf638c3ec6e99589812b9d231/release/share/git-webui/webui/img/git-icon.png -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/git-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alberthier/git-webui/dee7c192b2ec063cf638c3ec6e99589812b9d231/release/share/git-webui/webui/img/git-logo.png -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | Gnome Symbolic Icon Theme 27 | 28 | 29 | 30 | 60 | 69 | 70 | Gnome Symbolic Icon Theme 72 | 74 | 80 | 85 | 90 | 95 | 100 | 104 | 122 | 123 | 128 | 134 | 140 | 141 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/img/tag.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /release/share/git-webui/webui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | Git WebUI 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 44 | 45 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /release/share/git-webui/webui/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('