Commit 61cfa2a7a6e1d4557b69e17c537656e8a0201ac8
Exists in
master
and in
4 other branches
Merge branch 'master' into fixes/api
Conflicts: lib/api/projects.rb
Showing
147 changed files
with
1924 additions
and
1221 deletions
Show diff stats
CONTRIBUTING.md
1 | -# Contact & support | |
1 | +# Contribute to GitLab | |
2 | 2 | |
3 | -If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq). | |
4 | -Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues. | |
3 | +If you have a question or want to contribute to GitLab this guide show you the appropriate channel to use. | |
5 | 4 | |
5 | +## Ruling out common errors | |
6 | 6 | |
7 | +Some errors are common and it may so happen, that you are not the only one who stumbled over a particular issue. We have [collected several of those and documented quick solutions](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) for them. | |
7 | 8 | |
8 | -# Contribute to GitLab | |
9 | +## Support forum | |
10 | + | |
11 | +Please visit our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) for any kind of question regarding the usage or adiministration/configuration of GitLab. | |
12 | + | |
13 | +### Use the support forum if ... | |
9 | 14 | |
10 | -## Recipes | |
15 | +* You get permission denied errors | |
16 | +* You can't see your repos | |
17 | +* You have issues cloning, pulling or pushing | |
18 | +* You have issues with web_hooks not firing | |
11 | 19 | |
12 | -We collect user submitted installation scripts and config file templates for platforms we don't support officially. | |
13 | -We believe there is merit in allowing a certain amount of diversity. | |
14 | -You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc. | |
20 | +**Search** for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and had it resolved. | |
15 | 21 | |
16 | -Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/) | |
22 | +## Paid support | |
17 | 23 | |
24 | +Community support in the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) is done by volunteers. Paid support is available from [GitLab.com](http://blog.gitlab.com/services/) | |
18 | 25 | |
19 | 26 | ## Feature suggestions |
20 | 27 | |
21 | -Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own. | |
28 | +Feature suggestions don't belong in issues but can go to [Feedback forum](http://gitlab.uservoice.com/forums/176466-general) where they can be voted on. | |
29 | + | |
30 | +## Pull requests | |
31 | + | |
32 | +Code speaks louder than words. If you can please submit a pull request with the fix including tests. The workflow to make a pull request is as follows: | |
33 | + | |
34 | +1. Fork the project on GitHub | |
35 | +1. Create a feature branch | |
36 | +1. Write tests and code | |
37 | +1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) | |
38 | +1. Push the commit to your fork | |
39 | +1. Submit a pull request | |
40 | + | |
41 | +We will accept pull requests if: | |
42 | + | |
43 | +* The code has proper tests and all tests pass | |
44 | +* It can be merged without problems (if not please use: git rebase master) | |
45 | +* It doesn't break any existing functionality | |
46 | +* It's quality code that conforms to the [Rails style guide](https://github.com/bbatsov/rails-style-guide) and best practices | |
47 | +* The description includes a motive for your change and the method you used to achieve it | |
48 | +* It keeps the GitLab code base clean and well structured | |
49 | +* We think other users will need the same functionality | |
50 | +* If it makes changes to the UI the pull request should include screenshots | |
51 | + | |
52 | +For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed). | |
53 | + | |
54 | +## Submitting via GitHub's issue tracker | |
55 | + | |
56 | +* For obvious bugs or misbehavior in GitLab in the master branch. Please include the revision id and a reproducible test case. | |
57 | +* For problematic or insufficient documentation. Please give a suggestion on how to improve it. | |
58 | + | |
59 | +If you're unsure where to post, post it to the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) first. | |
60 | +There are a lot of helpful GitLab users there who may be able to help you quickly. | |
61 | +If your particular issue turns out to be a bug, it will find its way from there to the [issue tracker on GitHub](https://github.com/gitlabhq/gitlabhq/issues). | |
62 | + | |
63 | +### When submitting an issue | |
64 | + | |
65 | +**Search** for similar entries before submitting your own, there's a good chance somebody else had the same issue or idea. Show your support with `:+1:` and/or join the discussion. | |
66 | + | |
67 | +Please consider the following points when submitting an **issue**: | |
22 | 68 | |
69 | +* Summarize your issue in one sentence (what happened wrong, when you did/expected something else) | |
70 | +* Describe your issue in detail (including steps to reproduce) | |
71 | +* Add logs or screen shots when possible | |
72 | +* Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`) | |
23 | 73 | |
24 | -## Code | |
74 | +## Thank you! | |
25 | 75 | |
26 | -Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab. | |
76 | +By taking the time to use the right channel, you help the development team to organize and prioritize issues and suggestions in order to make GitLab a better product for us all. | ... | ... |
Gemfile
... | ... | @@ -15,16 +15,18 @@ gem "mysql2", group: :mysql |
15 | 15 | gem "pg", group: :postgres |
16 | 16 | |
17 | 17 | # Auth |
18 | -gem "devise", "~> 2.1.0" | |
19 | -gem 'omniauth', "~> 1.1.1" | |
18 | +gem "devise" | |
19 | +gem 'omniauth', "~> 1.1.3" | |
20 | 20 | gem 'omniauth-google-oauth2' |
21 | 21 | gem 'omniauth-twitter' |
22 | 22 | gem 'omniauth-github' |
23 | 23 | |
24 | -# GITLAB patched libs | |
25 | -gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '9e98418ce2d654485b967003726aa2706a10060b' | |
26 | -gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8' | |
27 | -gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e' | |
24 | +# Extracting information from a git repository | |
25 | +gem "gitlab-grit", '~> 1.0.0', require: 'grit' | |
26 | +gem 'grit_ext', '~> 0.6.2' | |
27 | + | |
28 | +# Ruby/Rack Git Smart-HTTP Server Handler | |
29 | +gem 'gitlab-grack', '~> 1.0.0', require: 'grack' | |
28 | 30 | |
29 | 31 | # LDAP Auth |
30 | 32 | gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap" |
... | ... | @@ -33,7 +35,7 @@ gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap" |
33 | 35 | gem 'gitlab_yaml_db', '1.0.0', require: "yaml_db" |
34 | 36 | |
35 | 37 | # Syntax highlighter |
36 | -gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master" | |
38 | +gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb' | |
37 | 39 | |
38 | 40 | # Language detection |
39 | 41 | gem "github-linguist", "~> 2.3.4" , require: "linguist" |
... | ... | @@ -46,14 +48,17 @@ gem "grape-entity", "~> 0.2.0" |
46 | 48 | # based on human-friendly examples |
47 | 49 | gem "stamp" |
48 | 50 | |
51 | +# Enumeration fields | |
52 | +gem 'enumerize' | |
53 | + | |
49 | 54 | # Pagination |
50 | 55 | gem "kaminari", "~> 0.14.1" |
51 | 56 | |
52 | 57 | # HAML |
53 | -gem "haml-rails", "~> 0.3.5" | |
58 | +gem "haml-rails" | |
54 | 59 | |
55 | 60 | # Files attachments |
56 | -gem "carrierwave", "~> 0.7.1" | |
61 | +gem "carrierwave" | |
57 | 62 | |
58 | 63 | # Authorization |
59 | 64 | gem "six" |
... | ... | @@ -69,7 +74,7 @@ gem "redcarpet", "~> 2.2.2" |
69 | 74 | gem "github-markup", "~> 0.7.4", require: 'github/markup' |
70 | 75 | |
71 | 76 | # Servers |
72 | -gem "unicorn", "~> 4.4.0" | |
77 | +gem "unicorn" | |
73 | 78 | |
74 | 79 | # State machine |
75 | 80 | gem "state_machine" |
... | ... | @@ -78,12 +83,12 @@ gem "state_machine" |
78 | 83 | gem "acts-as-taggable-on", "2.3.3" |
79 | 84 | |
80 | 85 | # Decorators |
81 | -gem "draper", "~> 0.18.0" | |
86 | +gem "draper" | |
82 | 87 | |
83 | 88 | # Background jobs |
84 | 89 | gem 'slim' |
85 | 90 | gem 'sinatra', require: nil |
86 | -gem 'sidekiq', '2.7.3' | |
91 | +gem 'sidekiq' | |
87 | 92 | |
88 | 93 | # HTTP requests |
89 | 94 | gem "httparty" |
... | ... | @@ -113,6 +118,7 @@ group :assets do |
113 | 118 | gem 'bootstrap-sass', "2.2.1.1" |
114 | 119 | gem "font-awesome-sass-rails", "~> 3.0.0" |
115 | 120 | gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' |
121 | + gem "gon" | |
116 | 122 | end |
117 | 123 | |
118 | 124 | group :development do |
... | ... | @@ -140,7 +146,7 @@ group :development, :test do |
140 | 146 | gem "capybara", '2.0.2' |
141 | 147 | gem "pry" |
142 | 148 | gem "awesome_print" |
143 | - gem "database_cleaner", ref: "9f898fc50d87a5d51760f9dcf374bf5ffda21baf", git: "https://github.com/bmabey/database_cleaner.git" | |
149 | + gem "database_cleaner" | |
144 | 150 | gem "launchy" |
145 | 151 | gem 'factory_girl_rails' |
146 | 152 | ... | ... |
Gemfile.lock
1 | 1 | GIT |
2 | - remote: https://github.com/bmabey/database_cleaner.git | |
3 | - revision: 9f898fc50d87a5d51760f9dcf374bf5ffda21baf | |
4 | - ref: 9f898fc50d87a5d51760f9dcf374bf5ffda21baf | |
5 | - specs: | |
6 | - database_cleaner (0.9.1) | |
7 | - | |
8 | -GIT | |
9 | 2 | remote: https://github.com/ctran/annotate_models.git |
10 | 3 | revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e |
11 | 4 | specs: |
... | ... | @@ -14,41 +7,6 @@ GIT |
14 | 7 | rake (>= 0.8.7) |
15 | 8 | |
16 | 9 | GIT |
17 | - remote: https://github.com/gitlabhq/grack.git | |
18 | - revision: ba46f3b0845c6a09d488ae6abdce6ede37e227e8 | |
19 | - ref: ba46f3b0845c6a09d488ae6abdce6ede37e227e8 | |
20 | - specs: | |
21 | - grack (1.0.0) | |
22 | - rack (~> 1.4.1) | |
23 | - | |
24 | -GIT | |
25 | - remote: https://github.com/gitlabhq/grit.git | |
26 | - revision: 9e98418ce2d654485b967003726aa2706a10060b | |
27 | - ref: 9e98418ce2d654485b967003726aa2706a10060b | |
28 | - specs: | |
29 | - grit (2.5.0) | |
30 | - diff-lcs (~> 1.1) | |
31 | - mime-types (~> 1.15) | |
32 | - posix-spawn (~> 0.3.6) | |
33 | - | |
34 | -GIT | |
35 | - remote: https://github.com/gitlabhq/grit_ext.git | |
36 | - revision: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e | |
37 | - ref: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e | |
38 | - specs: | |
39 | - grit_ext (0.6.1) | |
40 | - charlock_holmes (~> 0.6.9) | |
41 | - | |
42 | -GIT | |
43 | - remote: https://github.com/gitlabhq/pygments.rb.git | |
44 | - revision: db1da0343adf86b49bdc3add04d02d2e80438d38 | |
45 | - branch: master | |
46 | - specs: | |
47 | - pygments.rb (0.3.2) | |
48 | - posix-spawn (~> 0.3.6) | |
49 | - yajl-ruby (~> 1.1.0) | |
50 | - | |
51 | -GIT | |
52 | 10 | remote: https://github.com/gitlabhq/raphael-rails.git |
53 | 11 | revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58 |
54 | 12 | specs: |
... | ... | @@ -89,12 +47,13 @@ GEM |
89 | 47 | addressable (2.3.2) |
90 | 48 | arel (3.0.2) |
91 | 49 | awesome_print (1.1.0) |
92 | - backports (2.6.5) | |
50 | + backports (2.6.7) | |
93 | 51 | bcrypt-ruby (3.0.1) |
94 | 52 | better_errors (0.3.2) |
95 | 53 | coderay (>= 1.0.0) |
96 | 54 | erubis (>= 2.7.0) |
97 | - binding_of_caller (0.6.8) | |
55 | + binding_of_caller (0.7.1) | |
56 | + debug_inspector (>= 0.0.1) | |
98 | 57 | bootstrap-sass (2.2.1.1) |
99 | 58 | sass (~> 3.2) |
100 | 59 | builder (3.0.4) |
... | ... | @@ -105,7 +64,7 @@ GEM |
105 | 64 | rack-test (>= 0.5.4) |
106 | 65 | selenium-webdriver (~> 2.0) |
107 | 66 | xpath (~> 1.0.0) |
108 | - carrierwave (0.7.1) | |
67 | + carrierwave (0.8.0) | |
109 | 68 | activemodel (>= 3.2.0) |
110 | 69 | activesupport (>= 3.2.0) |
111 | 70 | celluloid (0.12.4) |
... | ... | @@ -132,18 +91,24 @@ GEM |
132 | 91 | connection_pool (1.0.0) |
133 | 92 | crack (0.3.1) |
134 | 93 | daemons (1.1.9) |
135 | - devise (2.1.2) | |
94 | + database_cleaner (0.9.1) | |
95 | + debug_inspector (0.0.2) | |
96 | + descendants_tracker (0.0.1) | |
97 | + devise (2.2.3) | |
136 | 98 | bcrypt-ruby (~> 3.0) |
137 | 99 | orm_adapter (~> 0.1) |
138 | 100 | railties (~> 3.1) |
139 | 101 | warden (~> 1.2.1) |
140 | 102 | diff-lcs (1.1.3) |
141 | - draper (0.18.0) | |
142 | - actionpack (~> 3.2) | |
143 | - activesupport (~> 3.2) | |
103 | + draper (1.1.0) | |
104 | + actionpack (>= 3.0) | |
105 | + activesupport (>= 3.0) | |
106 | + request_store (~> 1.0.3) | |
144 | 107 | email_spec (1.4.0) |
145 | 108 | launchy (~> 2.1) |
146 | 109 | mail (~> 2.2) |
110 | + enumerize (0.5.1) | |
111 | + activesupport (>= 3.2) | |
147 | 112 | erubis (2.7.0) |
148 | 113 | escape_utils (0.2.4) |
149 | 114 | eventmachine (1.0.0) |
... | ... | @@ -155,7 +120,7 @@ GEM |
155 | 120 | factory_girl_rails (4.1.0) |
156 | 121 | factory_girl (~> 4.1.0) |
157 | 122 | railties (>= 3.0.0) |
158 | - faraday (0.8.4) | |
123 | + faraday (0.8.6) | |
159 | 124 | multipart-post (~> 1.1) |
160 | 125 | faye-websocket (0.4.7) |
161 | 126 | eventmachine (>= 0.12.0) |
... | ... | @@ -164,7 +129,7 @@ GEM |
164 | 129 | font-awesome-sass-rails (3.0.0.1) |
165 | 130 | railties (>= 3.1.1) |
166 | 131 | sass-rails (>= 3.1.1) |
167 | - foreman (0.60.2) | |
132 | + foreman (0.61.0) | |
168 | 133 | thor (>= 0.13.6) |
169 | 134 | gemoji (1.2.1) |
170 | 135 | gherkin-ruby (0.2.1) |
... | ... | @@ -174,7 +139,16 @@ GEM |
174 | 139 | escape_utils (~> 0.2.3) |
175 | 140 | mime-types (~> 1.19) |
176 | 141 | pygments.rb (>= 0.2.13) |
177 | - github-markup (0.7.4) | |
142 | + github-markup (0.7.5) | |
143 | + gitlab-grack (1.0.0) | |
144 | + rack (~> 1.4.1) | |
145 | + gitlab-grit (1.0.0) | |
146 | + diff-lcs (~> 1.1) | |
147 | + mime-types (~> 1.15) | |
148 | + posix-spawn (~> 0.3.6) | |
149 | + gitlab-pygments.rb (0.3.2) | |
150 | + posix-spawn (~> 0.3.6) | |
151 | + yajl-ruby (~> 1.1.0) | |
178 | 152 | gitlab_meta (5.0) |
179 | 153 | gitlab_omniauth-ldap (1.0.2) |
180 | 154 | net-ldap (~> 0.2.2) |
... | ... | @@ -182,17 +156,22 @@ GEM |
182 | 156 | pyu-ruby-sasl (~> 0.0.3.1) |
183 | 157 | rubyntlm (~> 0.1.1) |
184 | 158 | gitlab_yaml_db (1.0.0) |
185 | - grape (0.3.1) | |
159 | + gon (4.0.2) | |
160 | + grape (0.3.2) | |
186 | 161 | activesupport |
187 | - grape-entity (~> 0.2.0) | |
188 | - hashie (~> 1.2) | |
162 | + builder | |
163 | + hashie (>= 1.2.0) | |
189 | 164 | multi_json (>= 1.3.2) |
190 | - multi_xml | |
165 | + multi_xml (>= 0.5.2) | |
191 | 166 | rack |
192 | 167 | rack-accept |
193 | 168 | rack-mount |
194 | 169 | virtus |
195 | 170 | grape-entity (0.2.0) |
171 | + activesupport | |
172 | + multi_json (>= 1.3.2) | |
173 | + grit_ext (0.6.2) | |
174 | + charlock_holmes (~> 0.6.9) | |
196 | 175 | growl (1.0.3) |
197 | 176 | guard (1.5.4) |
198 | 177 | listen (>= 0.4.2) |
... | ... | @@ -205,20 +184,21 @@ GEM |
205 | 184 | guard-spinach (0.0.2) |
206 | 185 | guard (>= 1.1) |
207 | 186 | spinach |
208 | - haml (3.1.7) | |
209 | - haml-rails (0.3.5) | |
187 | + haml (4.0.0) | |
188 | + tilt | |
189 | + haml-rails (0.4) | |
210 | 190 | actionpack (>= 3.1, < 4.1) |
211 | 191 | activesupport (>= 3.1, < 4.1) |
212 | - haml (~> 3.1) | |
192 | + haml (>= 3.1, < 4.1) | |
213 | 193 | railties (>= 3.1, < 4.1) |
214 | 194 | hashie (1.2.0) |
215 | 195 | hike (1.2.1) |
216 | 196 | http_parser.rb (0.5.3) |
217 | - httparty (0.9.0) | |
197 | + httparty (0.10.2) | |
218 | 198 | multi_json (~> 1.0) |
219 | - multi_xml | |
199 | + multi_xml (>= 0.5.2) | |
220 | 200 | httpauth (0.2.0) |
221 | - i18n (0.6.1) | |
201 | + i18n (0.6.4) | |
222 | 202 | journey (1.0.4) |
223 | 203 | jquery-atwho-rails (0.1.7) |
224 | 204 | jquery-rails (2.1.3) |
... | ... | @@ -233,7 +213,7 @@ GEM |
233 | 213 | kaminari (0.14.1) |
234 | 214 | actionpack (>= 3.0.0) |
235 | 215 | activesupport (>= 3.0.0) |
236 | - kgio (2.7.4) | |
216 | + kgio (2.8.0) | |
237 | 217 | launchy (2.1.2) |
238 | 218 | addressable (~> 2.3) |
239 | 219 | letter_opener (1.0.0) |
... | ... | @@ -250,22 +230,22 @@ GEM |
250 | 230 | modernizr (2.6.2) |
251 | 231 | sprockets (~> 2.0) |
252 | 232 | multi_json (1.6.1) |
253 | - multi_xml (0.5.1) | |
254 | - multipart-post (1.1.5) | |
233 | + multi_xml (0.5.3) | |
234 | + multipart-post (1.2.0) | |
255 | 235 | mysql2 (0.3.11) |
256 | 236 | net-ldap (0.2.2) |
257 | 237 | nokogiri (1.5.6) |
258 | 238 | oauth (0.4.7) |
259 | - oauth2 (0.8.0) | |
239 | + oauth2 (0.8.1) | |
260 | 240 | faraday (~> 0.8) |
261 | 241 | httpauth (~> 0.1) |
262 | 242 | jwt (~> 0.1.4) |
263 | 243 | multi_json (~> 1.0) |
264 | 244 | rack (~> 1.2) |
265 | - omniauth (1.1.1) | |
245 | + omniauth (1.1.3) | |
266 | 246 | hashie (~> 1.2) |
267 | 247 | rack |
268 | - omniauth-github (1.0.3) | |
248 | + omniauth-github (1.1.0) | |
269 | 249 | omniauth (~> 1.0) |
270 | 250 | omniauth-oauth2 (~> 1.1) |
271 | 251 | omniauth-google-oauth2 (0.1.13) |
... | ... | @@ -293,6 +273,9 @@ GEM |
293 | 273 | coderay (~> 1.0.5) |
294 | 274 | method_source (~> 0.8) |
295 | 275 | slop (~> 3.3.1) |
276 | + pygments.rb (0.4.2) | |
277 | + posix-spawn (~> 0.3.6) | |
278 | + yajl-ruby (~> 1.1.0) | |
296 | 279 | pyu-ruby-sasl (0.0.3.3) |
297 | 280 | quiet_assets (1.0.1) |
298 | 281 | railties (~> 3.1) |
... | ... | @@ -305,7 +288,7 @@ GEM |
305 | 288 | rack (>= 1.1.3) |
306 | 289 | rack-mount (0.8.3) |
307 | 290 | rack (>= 1.0.0) |
308 | - rack-protection (1.3.2) | |
291 | + rack-protection (1.4.0) | |
309 | 292 | rack |
310 | 293 | rack-ssl (1.3.3) |
311 | 294 | rack |
... | ... | @@ -342,12 +325,13 @@ GEM |
342 | 325 | rb-fsevent (0.9.2) |
343 | 326 | rb-inotify (0.8.8) |
344 | 327 | ffi (>= 0.5.0) |
345 | - rdoc (3.12.1) | |
328 | + rdoc (3.12.2) | |
346 | 329 | json (~> 1.4) |
347 | 330 | redcarpet (2.2.2) |
348 | 331 | redis (3.0.2) |
349 | 332 | redis-namespace (1.2.1) |
350 | 333 | redis (~> 3.0.0) |
334 | + request_store (1.0.5) | |
351 | 335 | rspec (2.12.0) |
352 | 336 | rspec-core (~> 2.12.0) |
353 | 337 | rspec-expectations (~> 2.12.0) |
... | ... | @@ -381,11 +365,11 @@ GEM |
381 | 365 | multi_json (~> 1.0) |
382 | 366 | rubyzip |
383 | 367 | websocket (~> 1.0.4) |
384 | - settingslogic (2.0.8) | |
368 | + settingslogic (2.0.9) | |
385 | 369 | sexp_processor (4.1.3) |
386 | 370 | shoulda-matchers (1.3.0) |
387 | 371 | activesupport (>= 3.0.0) |
388 | - sidekiq (2.7.3) | |
372 | + sidekiq (2.7.5) | |
389 | 373 | celluloid (~> 0.12.0) |
390 | 374 | connection_pool (~> 1.0) |
391 | 375 | multi_json (~> 1) |
... | ... | @@ -395,9 +379,9 @@ GEM |
395 | 379 | multi_json (~> 1.0) |
396 | 380 | simplecov-html (~> 0.7.1) |
397 | 381 | simplecov-html (0.7.1) |
398 | - sinatra (1.3.3) | |
399 | - rack (~> 1.3, >= 1.3.6) | |
400 | - rack-protection (~> 1.2) | |
382 | + sinatra (1.3.5) | |
383 | + rack (~> 1.4) | |
384 | + rack-protection (~> 1.3) | |
401 | 385 | tilt (~> 1.3, >= 1.3.3) |
402 | 386 | six (0.2.0) |
403 | 387 | slim (1.3.6) |
... | ... | @@ -416,7 +400,7 @@ GEM |
416 | 400 | multi_json (~> 1.0) |
417 | 401 | rack (~> 1.0) |
418 | 402 | tilt (~> 1.1, != 1.3.0) |
419 | - stamp (0.3.0) | |
403 | + stamp (0.5.0) | |
420 | 404 | state_machine (1.1.2) |
421 | 405 | temple (0.5.5) |
422 | 406 | test_after_commit (0.0.1) |
... | ... | @@ -427,7 +411,7 @@ GEM |
427 | 411 | eventmachine (>= 0.12.6) |
428 | 412 | rack (>= 1.0.0) |
429 | 413 | thor (0.17.0) |
430 | - tilt (1.3.3) | |
414 | + tilt (1.3.4) | |
431 | 415 | timers (1.1.0) |
432 | 416 | treetop (1.4.12) |
433 | 417 | polyglot |
... | ... | @@ -436,12 +420,13 @@ GEM |
436 | 420 | uglifier (1.3.0) |
437 | 421 | execjs (>= 0.3.0) |
438 | 422 | multi_json (~> 1.0, >= 1.0.2) |
439 | - unicorn (4.4.0) | |
423 | + unicorn (4.6.2) | |
440 | 424 | kgio (~> 2.6) |
441 | 425 | rack |
442 | 426 | raindrops (~> 0.7) |
443 | - virtus (0.5.2) | |
427 | + virtus (0.5.4) | |
444 | 428 | backports (~> 2.6.1) |
429 | + descendants_tracker (~> 0.0.1) | |
445 | 430 | warden (1.2.1) |
446 | 431 | rack (>= 1.0) |
447 | 432 | webmock (1.9.0) |
... | ... | @@ -463,14 +448,15 @@ DEPENDENCIES |
463 | 448 | binding_of_caller |
464 | 449 | bootstrap-sass (= 2.2.1.1) |
465 | 450 | capybara (= 2.0.2) |
466 | - carrierwave (~> 0.7.1) | |
451 | + carrierwave | |
467 | 452 | chosen-rails (= 0.9.8) |
468 | 453 | coffee-rails (~> 3.2.2) |
469 | 454 | colored |
470 | - database_cleaner! | |
471 | - devise (~> 2.1.0) | |
472 | - draper (~> 0.18.0) | |
455 | + database_cleaner | |
456 | + devise | |
457 | + draper | |
473 | 458 | email_spec |
459 | + enumerize | |
474 | 460 | factory_girl_rails |
475 | 461 | ffaker |
476 | 462 | font-awesome-sass-rails (~> 3.0.0) |
... | ... | @@ -479,18 +465,20 @@ DEPENDENCIES |
479 | 465 | git |
480 | 466 | github-linguist (~> 2.3.4) |
481 | 467 | github-markup (~> 0.7.4) |
468 | + gitlab-grack (~> 1.0.0) | |
469 | + gitlab-grit (~> 1.0.0) | |
470 | + gitlab-pygments.rb (~> 0.3.2) | |
482 | 471 | gitlab_meta (= 5.0) |
483 | 472 | gitlab_omniauth-ldap (= 1.0.2) |
484 | 473 | gitlab_yaml_db (= 1.0.0) |
485 | - grack! | |
474 | + gon | |
486 | 475 | grape (~> 0.3.1) |
487 | 476 | grape-entity (~> 0.2.0) |
488 | - grit! | |
489 | - grit_ext! | |
477 | + grit_ext (~> 0.6.2) | |
490 | 478 | growl |
491 | 479 | guard-rspec |
492 | 480 | guard-spinach |
493 | - haml-rails (~> 0.3.5) | |
481 | + haml-rails | |
494 | 482 | httparty |
495 | 483 | jquery-atwho-rails (= 0.1.7) |
496 | 484 | jquery-rails (= 2.1.3) |
... | ... | @@ -500,14 +488,13 @@ DEPENDENCIES |
500 | 488 | letter_opener |
501 | 489 | modernizr (= 2.6.2) |
502 | 490 | mysql2 |
503 | - omniauth (~> 1.1.1) | |
491 | + omniauth (~> 1.1.3) | |
504 | 492 | omniauth-github |
505 | 493 | omniauth-google-oauth2 |
506 | 494 | omniauth-twitter |
507 | 495 | pg |
508 | 496 | poltergeist (= 1.1.0) |
509 | 497 | pry |
510 | - pygments.rb! | |
511 | 498 | quiet_assets (~> 1.0.1) |
512 | 499 | rack-mini-profiler |
513 | 500 | rails (= 3.2.12) |
... | ... | @@ -523,7 +510,7 @@ DEPENDENCIES |
523 | 510 | seed-fu |
524 | 511 | settingslogic |
525 | 512 | shoulda-matchers (= 1.3.0) |
526 | - sidekiq (= 2.7.3) | |
513 | + sidekiq | |
527 | 514 | simplecov |
528 | 515 | sinatra |
529 | 516 | six |
... | ... | @@ -535,5 +522,5 @@ DEPENDENCIES |
535 | 522 | therubyracer |
536 | 523 | thin |
537 | 524 | uglifier (~> 1.3.0) |
538 | - unicorn (~> 4.4.0) | |
525 | + unicorn | |
539 | 526 | webmock | ... | ... |
README.md
1 | -# Welcome to GitLab! Self hosted Git management software | |
1 | +## GitLab: self hosted Git management software | |
2 | 2 | |
3 | + | |
3 | 4 | |
4 | -## Badges: | |
5 | +### GitLab allows you to | |
6 | + * keep your code secure on your own server | |
7 | + * manage repositories, users and access permissions | |
8 | + * communicate though issues, line-comments and wiki's | |
9 | + * perform code reviews with merge requests | |
10 | + | |
11 | +### GitLab is | |
12 | + | |
13 | +* powered by Ruby on Rails | |
14 | +* completely free and open source (MIT license) | |
15 | +* used by 10.000 organization to keep their code secure | |
16 | + | |
17 | +### Code status | |
18 | + | |
19 | +* [](http://ci.gitlab.org/projects/1?ref=master) ci.gitlab.org (master branch) | |
20 | + | |
21 | +* [](https://travis-ci.org/gitlabhq/gitlabhq) travis-ci.org (master branch) | |
22 | + | |
23 | +* [](https://codeclimate.com/github/gitlabhq/gitlabhq) | |
5 | 24 | |
6 | -* master: travis-ci.org [](https://travis-ci.org/gitlabhq/gitlabhq)a | |
7 | -* master: ci.gitlab.org [](http://ci.gitlab.org/projects/1?ref=master) | |
8 | -* [](https://codeclimate.com/github/gitlabhq/gitlabhq) | |
9 | 25 | * [](https://gemnasium.com/gitlabhq/gitlabhq) |
10 | 26 | |
11 | -GitLab is a free project and repository management application | |
27 | +### Resources | |
12 | 28 | |
29 | +* GitLab.org community site: [Homepage](http://gitlab.org) [Screenshots](http://gitlab.org/screenshots/) [Blog](http://blog.gitlab.org/) [Demo](http://demo.gitlabhq.com/users/sign_in) | |
13 | 30 | |
14 | -## Application details | |
31 | +* GitLab.com: [Homepage](http://blog.gitlab.com/) [Hosted pricing](http://blog.gitlab.com/pricing/) [Services](http://blog.gitlab.com/services/) [Blog](http://blog.gitlab.com/blog/) | |
15 | 32 | |
16 | -* powered by Ruby on Rails | |
17 | -* its completely free and open source | |
18 | -* distributed under the MIT License | |
33 | +* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server | |
19 | 34 | |
20 | -## Requirements | |
35 | +### Requirements | |
21 | 36 | |
22 | -* Ubuntu/Debian | |
37 | +* Ubuntu/Debian* | |
23 | 38 | * ruby 1.9.3+ |
24 | 39 | * MySQL |
25 | 40 | * git |
26 | 41 | * gitlab-shell |
27 | 42 | * redis |
28 | 43 | |
29 | -## Install | |
44 | +* More details are in the [requirements doc](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md) | |
45 | + | |
46 | +### Installation | |
47 | + | |
48 | +You can either follow the "ordinary" Installation guide to install it on a machine or use the Vagrant virtual machine. The Installation guide is recommended to set up a production server. The Vargrant virtual machine is recommended for development since it makes it much easier to set up all the dependencies for integration testing. | |
49 | + | |
50 | +* [Installation guide for latest stable release](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md) | |
51 | + | |
52 | +* [Installation guide for the current master branch](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) | |
53 | + | |
54 | +* [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) | |
55 | + | |
56 | +### Starting | |
57 | + | |
58 | +1. The Installation guide contains instructions to download an init script and run that on boot. With the init script you can also start GitLab with: | |
59 | + | |
60 | + sudo service gitlab start | |
61 | + | |
62 | + or | |
63 | + | |
64 | + sudo /etc/init.d/gitlab restart | |
65 | + | |
66 | +2. Start it with [Foreman](https://github.com/ddollar/foreman) in development model | |
67 | + | |
68 | + bundle exec foreman start -p 3000 | |
69 | + | |
70 | +3. Start it manually in development mode | |
71 | + | |
72 | + bundle exec rails s | |
73 | + bundle exec rake sidekiq:start | |
74 | + | |
75 | +### Running the tests | |
76 | + | |
77 | +* Seed the database with | |
78 | + | |
79 | + bundle exec rake db:setup RAILS_ENV=test | |
80 | + bundle exec rake db:seed_fu RAILS_ENV=test | |
81 | + | |
82 | +* Run all tests | |
83 | + | |
84 | + bundle exec rake gitlab:test | |
85 | + | |
86 | +* Rspec unit and functional tests | |
87 | + | |
88 | + bundle exec rake spec | |
89 | + | |
90 | +* Spinach integration tests | |
91 | + | |
92 | + bundle exec rake spinach | |
93 | + | |
94 | +### Getting help | |
95 | + | |
96 | +* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) | |
97 | + | |
98 | +* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq) | |
99 | + | |
100 | +* [Feedback and suggestions forum](http://gitlab.uservoice.com/forums/176466-general) | |
101 | + | |
102 | +* [Paid support](http://blog.gitlab.com/support/) | |
103 | + | |
104 | +* [Paid services](http://blog.gitlab.com/services/) | |
105 | + | |
106 | +### New versions and the API | |
107 | + | |
108 | +Each month on the 22th a new version is released together with an upgrade guide. | |
109 | + | |
110 | +* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/wiki) | |
111 | + | |
112 | +* [Roadmap](https://github.com/gitlabhq/gitlabhq/blob/master/ROADMAP.md) | |
113 | + | |
114 | +### Other documentation | |
115 | + | |
116 | +* [GitLab API](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/README.md) | |
117 | + | |
118 | +* [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks) | |
119 | + | |
120 | +* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) | |
121 | + | |
122 | +### Getting in touch | |
30 | 123 | |
31 | -Checkout [wiki](https://github.com/gitlabhq/gitlabhq/wiki) pages for installation information, migration, etc. | |
124 | +* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) | |
32 | 125 | |
33 | -## [Community](http://gitlab.org/community/) | |
126 | +* [Core team](https://github.com/gitlabhq?tab=members) | |
34 | 127 | |
35 | -## [Contact](http://gitlab.org/contact/) | |
128 | +* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors) | |
36 | 129 | |
37 | -## Contribute | |
130 | +* [Leader](https://github.com/randx) | |
38 | 131 | |
39 | -[Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) | |
40 | -Want to help - send a pull request. | |
41 | -We'll accept good pull requests. | |
132 | +* [Contact page](http://gitlab.org/contact/) | ... | ... |
ROADMAP.md
app/assets/fonts/OFL.txt
... | ... | @@ -1,92 +0,0 @@ |
1 | -Copyright (c) 2010, Jan Gerner (post@yanone.de) | |
2 | -This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
3 | -This license is copied below, and is also available with a FAQ at: | |
4 | -http://scripts.sil.org/OFL | |
5 | - | |
6 | - | |
7 | ------------------------------------------------------------ | |
8 | -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
9 | ------------------------------------------------------------ | |
10 | - | |
11 | -PREAMBLE | |
12 | -The goals of the Open Font License (OFL) are to stimulate worldwide | |
13 | -development of collaborative font projects, to support the font creation | |
14 | -efforts of academic and linguistic communities, and to provide a free and | |
15 | -open framework in which fonts may be shared and improved in partnership | |
16 | -with others. | |
17 | - | |
18 | -The OFL allows the licensed fonts to be used, studied, modified and | |
19 | -redistributed freely as long as they are not sold by themselves. The | |
20 | -fonts, including any derivative works, can be bundled, embedded, | |
21 | -redistributed and/or sold with any software provided that any reserved | |
22 | -names are not used by derivative works. The fonts and derivatives, | |
23 | -however, cannot be released under any other type of license. The | |
24 | -requirement for fonts to remain under this license does not apply | |
25 | -to any document created using the fonts or their derivatives. | |
26 | - | |
27 | -DEFINITIONS | |
28 | -"Font Software" refers to the set of files released by the Copyright | |
29 | -Holder(s) under this license and clearly marked as such. This may | |
30 | -include source files, build scripts and documentation. | |
31 | - | |
32 | -"Reserved Font Name" refers to any names specified as such after the | |
33 | -copyright statement(s). | |
34 | - | |
35 | -"Original Version" refers to the collection of Font Software components as | |
36 | -distributed by the Copyright Holder(s). | |
37 | - | |
38 | -"Modified Version" refers to any derivative made by adding to, deleting, | |
39 | -or substituting -- in part or in whole -- any of the components of the | |
40 | -Original Version, by changing formats or by porting the Font Software to a | |
41 | -new environment. | |
42 | - | |
43 | -"Author" refers to any designer, engineer, programmer, technical | |
44 | -writer or other person who contributed to the Font Software. | |
45 | - | |
46 | -PERMISSION & CONDITIONS | |
47 | -Permission is hereby granted, free of charge, to any person obtaining | |
48 | -a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
49 | -redistribute, and sell modified and unmodified copies of the Font | |
50 | -Software, subject to the following conditions: | |
51 | - | |
52 | -1) Neither the Font Software nor any of its individual components, | |
53 | -in Original or Modified Versions, may be sold by itself. | |
54 | - | |
55 | -2) Original or Modified Versions of the Font Software may be bundled, | |
56 | -redistributed and/or sold with any software, provided that each copy | |
57 | -contains the above copyright notice and this license. These can be | |
58 | -included either as stand-alone text files, human-readable headers or | |
59 | -in the appropriate machine-readable metadata fields within text or | |
60 | -binary files as long as those fields can be easily viewed by the user. | |
61 | - | |
62 | -3) No Modified Version of the Font Software may use the Reserved Font | |
63 | -Name(s) unless explicit written permission is granted by the corresponding | |
64 | -Copyright Holder. This restriction only applies to the primary font name as | |
65 | -presented to the users. | |
66 | - | |
67 | -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
68 | -Software shall not be used to promote, endorse or advertise any | |
69 | -Modified Version, except to acknowledge the contribution(s) of the | |
70 | -Copyright Holder(s) and the Author(s) or with their explicit written | |
71 | -permission. | |
72 | - | |
73 | -5) The Font Software, modified or unmodified, in part or in whole, | |
74 | -must be distributed entirely under this license, and must not be | |
75 | -distributed under any other license. The requirement for fonts to | |
76 | -remain under this license does not apply to any document created | |
77 | -using the Font Software. | |
78 | - | |
79 | -TERMINATION | |
80 | -This license becomes null and void if any of the above conditions are | |
81 | -not met. | |
82 | - | |
83 | -DISCLAIMER | |
84 | -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
85 | -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
86 | -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
87 | -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
88 | -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
89 | -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
90 | -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
91 | -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
92 | -OTHER DEALINGS IN THE FONT SOFTWARE. |
app/assets/fonts/YanoneKaffeesatz-Light.ttf
No preview for this file type
... | ... | @@ -0,0 +1,400 @@ |
1 | +!function(){ | |
2 | + | |
3 | + var BranchGraph = function(element, options){ | |
4 | + this.element = element; | |
5 | + this.options = options; | |
6 | + | |
7 | + this.preparedCommits = {}; | |
8 | + this.mtime = 0; | |
9 | + this.mspace = 0; | |
10 | + this.parents = {}; | |
11 | + this.colors = ["#000"]; | |
12 | + | |
13 | + this.load(); | |
14 | + }; | |
15 | + | |
16 | + BranchGraph.prototype.load = function(){ | |
17 | + $.ajax({ | |
18 | + url: this.options.url, | |
19 | + method: 'get', | |
20 | + dataType: 'json', | |
21 | + success: $.proxy(function(data){ | |
22 | + $('.loading', this.element).hide(); | |
23 | + this.prepareData(data.days, data.commits); | |
24 | + this.buildGraph(); | |
25 | + }, this) | |
26 | + }); | |
27 | + }; | |
28 | + | |
29 | + BranchGraph.prototype.prepareData = function(days, commits){ | |
30 | + this.days = days; | |
31 | + this.dayCount = days.length; | |
32 | + this.commits = commits; | |
33 | + this.commitCount = commits.length; | |
34 | + | |
35 | + this.collectParents(); | |
36 | + | |
37 | + this.mtime += 4; | |
38 | + this.mspace += 10; | |
39 | + for (var i = 0; i < this.commitCount; i++) { | |
40 | + if (this.commits[i].id in this.parents) { | |
41 | + this.commits[i].isParent = true; | |
42 | + } | |
43 | + this.preparedCommits[this.commits[i].id] = this.commits[i]; | |
44 | + } | |
45 | + this.collectColors(); | |
46 | + }; | |
47 | + | |
48 | + BranchGraph.prototype.collectParents = function(){ | |
49 | + for (var i = 0; i < this.commitCount; i++) { | |
50 | + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { | |
51 | + this.parents[this.commits[i].parents[j][0]] = true; | |
52 | + } | |
53 | + this.mtime = Math.max(this.mtime, this.commits[i].time); | |
54 | + this.mspace = Math.max(this.mspace, this.commits[i].space); | |
55 | + } | |
56 | + }; | |
57 | + | |
58 | + BranchGraph.prototype.collectColors = function(){ | |
59 | + for (var k = 0; k < this.mspace; k++) { | |
60 | + this.colors.push(Raphael.getColor(.8)); | |
61 | + // Skipping a few colors in the spectrum to get more contrast between colors | |
62 | + Raphael.getColor();Raphael.getColor(); | |
63 | + } | |
64 | + }; | |
65 | + | |
66 | + BranchGraph.prototype.buildGraph = function(){ | |
67 | + var graphWidth = $(this.element).width() | |
68 | + , ch = this.mspace * 20 + 100 | |
69 | + , cw = Math.max(graphWidth, this.mtime * 20 + 260) | |
70 | + , r = Raphael(this.element.get(0), cw, ch) | |
71 | + , top = r.set() | |
72 | + , cuday = 0 | |
73 | + , cumonth = "" | |
74 | + , offsetX = 20 | |
75 | + , offsetY = 60 | |
76 | + , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320) | |
77 | + , scrollLeft = cw; | |
78 | + | |
79 | + this.raphael = r; | |
80 | + | |
81 | + r.rect(0, 0, barWidth, 20).attr({fill: "#222"}); | |
82 | + r.rect(0, 20, barWidth, 20).attr({fill: "#444"}); | |
83 | + | |
84 | + for (mm = 0; mm < this.dayCount; mm++) { | |
85 | + if(this.days[mm] != null){ | |
86 | + if(cuday != this.days[mm][0]){ | |
87 | + // Dates | |
88 | + r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({ | |
89 | + font: "12px Monaco, monospace", | |
90 | + fill: "#DDD" | |
91 | + }); | |
92 | + cuday = this.days[mm][0]; | |
93 | + } | |
94 | + if(cumonth != this.days[mm][1]){ | |
95 | + // Months | |
96 | + r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({ | |
97 | + font: "12px Monaco, monospace", | |
98 | + fill: "#EEE" | |
99 | + }); | |
100 | + cumonth = this.days[mm][1]; | |
101 | + } | |
102 | + } | |
103 | + } | |
104 | + | |
105 | + for (i = 0; i < this.commitCount; i++) { | |
106 | + var x = offsetX + 20 * this.commits[i].time | |
107 | + , y = offsetY + 10 * this.commits[i].space | |
108 | + , c | |
109 | + , ps; | |
110 | + | |
111 | + // Draw dot | |
112 | + r.circle(x, y, 3).attr({ | |
113 | + fill: this.colors[this.commits[i].space], | |
114 | + stroke: "none" | |
115 | + }); | |
116 | + | |
117 | + // Draw lines | |
118 | + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { | |
119 | + c = this.preparedCommits[this.commits[i].parents[j][0]]; | |
120 | + ps = this.commits[i].parent_spaces[j]; | |
121 | + if (c) { | |
122 | + var cx = offsetX + 20 * c.time | |
123 | + , cy = offsetY + 10 * c.space | |
124 | + , psy = offsetY + 10 * ps; | |
125 | + if (c.space == this.commits[i].space && c.space == ps) { | |
126 | + r.path([ | |
127 | + "M", x, y, | |
128 | + "L", cx, cy | |
129 | + ]).attr({ | |
130 | + stroke: this.colors[c.space], | |
131 | + "stroke-width": 2 | |
132 | + }); | |
133 | + | |
134 | + } else if (c.space < this.commits[i].space) { | |
135 | + if (y == psy) { | |
136 | + r.path([ | |
137 | + "M", x - 5, y, | |
138 | + "l-5,-2,0,4,5,-2", | |
139 | + "L", x - 10, y, | |
140 | + "L", x - 15, psy, | |
141 | + "L", cx + 5, psy, | |
142 | + "L", cx, cy]) | |
143 | + .attr({ | |
144 | + stroke: this.colors[this.commits[i].space], | |
145 | + "stroke-width": 2 | |
146 | + }); | |
147 | + } else { | |
148 | + r.path([ | |
149 | + "M", x - 3, y - 6, | |
150 | + "l-4,-3,4,-2,0,5", | |
151 | + "L", x - 5, y - 10, | |
152 | + "L", x - 10, psy, | |
153 | + "L", cx + 5, psy, | |
154 | + "L", cx, cy]) | |
155 | + .attr({ | |
156 | + stroke: this.colors[this.commits[i].space], | |
157 | + "stroke-width": 2 | |
158 | + }); | |
159 | + } | |
160 | + } else { | |
161 | + r.path([ | |
162 | + "M", x - 3, y + 6, | |
163 | + "l-4,3,4,2,0,-5", | |
164 | + "L", x - 5, y + 10, | |
165 | + "L", x - 10, psy, | |
166 | + "L", cx + 5, psy, | |
167 | + "L", cx, cy]) | |
168 | + .attr({ | |
169 | + stroke: this.colors[c.space], | |
170 | + "stroke-width": 2 | |
171 | + }); | |
172 | + } | |
173 | + } | |
174 | + } | |
175 | + | |
176 | + if (this.commits[i].refs) { | |
177 | + this.appendLabel(x, y, this.commits[i].refs); | |
178 | + } | |
179 | + | |
180 | + // mark commit and displayed in the center | |
181 | + if (this.commits[i].id == this.options.commit_id) { | |
182 | + r.path([ | |
183 | + 'M', x, y - 5, | |
184 | + 'L', x + 4, y - 15, | |
185 | + 'L', x - 4, y - 15, | |
186 | + 'Z' | |
187 | + ]).attr({ | |
188 | + "fill": "#000", | |
189 | + "fill-opacity": .7, | |
190 | + "stroke": "none" | |
191 | + }); | |
192 | + scrollLeft = x - graphWidth / 2; | |
193 | + } | |
194 | + | |
195 | + this.appendAnchor(top, this.commits[i], x, y); | |
196 | + } | |
197 | + top.toFront(); | |
198 | + this.element.scrollLeft(scrollLeft); | |
199 | + this.bindEvents(); | |
200 | + }; | |
201 | + | |
202 | + BranchGraph.prototype.bindEvents = function(){ | |
203 | + var drag = {} | |
204 | + , element = this.element; | |
205 | + | |
206 | + var dragger = function(event){ | |
207 | + element.scrollLeft(drag.sl - (event.clientX - drag.x)); | |
208 | + element.scrollTop(drag.st - (event.clientY - drag.y)); | |
209 | + }; | |
210 | + | |
211 | + element.on({ | |
212 | + mousedown: function (event) { | |
213 | + drag = { | |
214 | + x: event.clientX, | |
215 | + y: event.clientY, | |
216 | + st: element.scrollTop(), | |
217 | + sl: element.scrollLeft() | |
218 | + }; | |
219 | + $(window).on('mousemove', dragger); | |
220 | + } | |
221 | + }); | |
222 | + $(window).on({ | |
223 | + mouseup: function(){ | |
224 | + //bars.animate({opacity: 0}, 300); | |
225 | + $(window).off('mousemove', dragger); | |
226 | + }, | |
227 | + keydown: function(event){ | |
228 | + if(event.keyCode == 37){ | |
229 | + // left | |
230 | + element.scrollLeft( element.scrollLeft() - 50); | |
231 | + } | |
232 | + if(event.keyCode == 38){ | |
233 | + // top | |
234 | + element.scrollTop( element.scrollTop() - 50); | |
235 | + } | |
236 | + if(event.keyCode == 39){ | |
237 | + // right | |
238 | + element.scrollLeft( element.scrollLeft() + 50); | |
239 | + } | |
240 | + if(event.keyCode == 40){ | |
241 | + // bottom | |
242 | + element.scrollTop( element.scrollTop() + 50); | |
243 | + } | |
244 | + } | |
245 | + }); | |
246 | + }; | |
247 | + | |
248 | + BranchGraph.prototype.appendLabel = function(x, y, refs){ | |
249 | + var r = this.raphael | |
250 | + , shortrefs = refs | |
251 | + , text, textbox, rect; | |
252 | + | |
253 | + if (shortrefs.length > 17){ | |
254 | + // Truncate if longer than 15 chars | |
255 | + shortrefs = shortrefs.substr(0,15) + "…"; | |
256 | + } | |
257 | + | |
258 | + text = r.text(x+5, y+8 + 10, shortrefs).attr({ | |
259 | + font: "10px Monaco, monospace", | |
260 | + fill: "#FFF", | |
261 | + title: refs | |
262 | + }); | |
263 | + | |
264 | + textbox = text.getBBox(); | |
265 | + text.transform([ | |
266 | + 't', textbox.height/-4, textbox.width/2 + 5, | |
267 | + 'r90' | |
268 | + ]); | |
269 | + | |
270 | + // Create rectangle based on the size of the textbox | |
271 | + rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({ | |
272 | + "fill": "#000", | |
273 | + "fill-opacity": .7, | |
274 | + "stroke": "none" | |
275 | + }); | |
276 | + | |
277 | + triangle = r.path([ | |
278 | + 'M', x, y + 5, | |
279 | + 'L', x + 4, y + 15, | |
280 | + 'L', x - 4, y + 15, | |
281 | + 'Z' | |
282 | + ]).attr({ | |
283 | + "fill": "#000", | |
284 | + "fill-opacity": .7, | |
285 | + "stroke": "none" | |
286 | + }); | |
287 | + | |
288 | + // Rotate and reposition rectangle over text | |
289 | + rect.transform([ | |
290 | + 'r', 90, x, y, | |
291 | + 't', 15, -9 | |
292 | + ]); | |
293 | + | |
294 | + // Set text to front | |
295 | + text.toFront(); | |
296 | + }; | |
297 | + | |
298 | + BranchGraph.prototype.appendAnchor = function(top, commit, x, y) { | |
299 | + var r = this.raphael | |
300 | + , options = this.options | |
301 | + , anchor; | |
302 | + anchor = r.circle(x, y, 10).attr({ | |
303 | + fill: "#000", | |
304 | + opacity: 0, | |
305 | + cursor: "pointer" | |
306 | + }) | |
307 | + .click(function(){ | |
308 | + window.open(options.commit_url.replace('%s', commit.id), '_blank'); | |
309 | + }) | |
310 | + .hover(function(){ | |
311 | + this.tooltip = r.commitTooltip(x, y + 5, commit); | |
312 | + top.push(this.tooltip.insertBefore(this)); | |
313 | + }, function(){ | |
314 | + this.tooltip && this.tooltip.remove() && delete this.tooltip; | |
315 | + }); | |
316 | + top.push(anchor); | |
317 | + }; | |
318 | + | |
319 | + this.BranchGraph = BranchGraph; | |
320 | + | |
321 | +}(this); | |
322 | +Raphael.fn.commitTooltip = function(x, y, commit){ | |
323 | + var icon, nameText, idText, messageText | |
324 | + , boxWidth = 300 | |
325 | + , boxHeight = 200; | |
326 | + | |
327 | + icon = this.image(commit.author.icon, x, y, 20, 20); | |
328 | + nameText = this.text(x + 25, y + 10, commit.author.name); | |
329 | + idText = this.text(x, y + 35, commit.id); | |
330 | + messageText = this.text(x, y + 50, commit.message); | |
331 | + | |
332 | + textSet = this.set(icon, nameText, idText, messageText).attr({ | |
333 | + "text-anchor": "start", | |
334 | + "font": "12px Monaco, monospace" | |
335 | + }); | |
336 | + | |
337 | + nameText.attr({ | |
338 | + "font": "14px Arial", | |
339 | + "font-weight": "bold" | |
340 | + }); | |
341 | + | |
342 | + idText.attr({ | |
343 | + "fill": "#AAA" | |
344 | + }); | |
345 | + | |
346 | + textWrap(messageText, boxWidth - 50); | |
347 | + | |
348 | + var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ | |
349 | + "fill": "#FFF", | |
350 | + "stroke": "#000", | |
351 | + "stroke-linecap": "round", | |
352 | + "stroke-width": 2 | |
353 | + }); | |
354 | + var tooltip = this.set(rect, textSet); | |
355 | + | |
356 | + rect.attr({ | |
357 | + "height" : tooltip.getBBox().height + 10, | |
358 | + "width" : tooltip.getBBox().width + 10 | |
359 | + }); | |
360 | + | |
361 | + tooltip.transform([ | |
362 | + 't', 20, 20 | |
363 | + ]); | |
364 | + | |
365 | + return tooltip; | |
366 | +}; | |
367 | + | |
368 | +function textWrap(t, width) { | |
369 | + var content = t.attr("text"); | |
370 | + var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
371 | + t.attr({ | |
372 | + "text" : abc | |
373 | + }); | |
374 | + var letterWidth = t.getBBox().width / abc.length; | |
375 | + | |
376 | + t.attr({ | |
377 | + "text" : content | |
378 | + }); | |
379 | + | |
380 | + var words = content.split(" "); | |
381 | + var x = 0, s = []; | |
382 | + for ( var i = 0; i < words.length; i++) { | |
383 | + | |
384 | + var l = words[i].length; | |
385 | + if (x + (l * letterWidth) > width) { | |
386 | + s.push("\n"); | |
387 | + x = 0; | |
388 | + } | |
389 | + x += l * letterWidth; | |
390 | + s.push(words[i] + " "); | |
391 | + } | |
392 | + t.attr({ | |
393 | + "text" : s.join("") | |
394 | + }); | |
395 | + var b = t.getBBox() | |
396 | + , h = Math.abs(b.y2) - Math.abs(b.y) + 1; | |
397 | + t.attr({ | |
398 | + "y": b.y + h | |
399 | + }); | |
400 | +} | ... | ... |
app/assets/javascripts/main.js.coffee
... | ... | @@ -54,10 +54,10 @@ $ -> |
54 | 54 | $(@).parents('form').submit() |
55 | 55 | |
56 | 56 | # Flash |
57 | - if (flash = $("#flash-container")).length > 0 | |
58 | - flash.click -> $(@).slideUp("slow") | |
59 | - flash.slideDown "slow" | |
60 | - setTimeout (-> flash.slideUp("slow")), 3000 | |
57 | + if (flash = $(".flash-container")).length > 0 | |
58 | + flash.click -> $(@).fadeOut() | |
59 | + flash.show() | |
60 | + setTimeout (-> flash.fadeOut()), 3000 | |
61 | 61 | |
62 | 62 | # Disable form buttons while a form is submitting |
63 | 63 | $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> | ... | ... |
app/assets/javascripts/projects.js.coffee
... | ... | @@ -18,3 +18,18 @@ $ -> |
18 | 18 | # Ref switcher |
19 | 19 | $('.project-refs-select').on 'change', -> |
20 | 20 | $(@).parents('form').submit() |
21 | + | |
22 | + $('#project_issues_enabled').change -> | |
23 | + if ($(this).is(':checked') == true) | |
24 | + $('#project_issues_tracker').removeAttr('disabled') | |
25 | + else | |
26 | + $('#project_issues_tracker').attr('disabled', 'disabled') | |
27 | + | |
28 | + $('#project_issues_tracker').change() | |
29 | + | |
30 | + $('#project_issues_tracker').change -> | |
31 | + if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) | |
32 | + $('#project_issues_tracker_id').attr('disabled', 'disabled') | |
33 | + else | |
34 | + $('#project_issues_tracker_id').removeAttr('disabled') | |
35 | + | ... | ... |
app/assets/stylesheets/common.scss
... | ... | @@ -67,27 +67,17 @@ table a code { |
67 | 67 | } |
68 | 68 | |
69 | 69 | /** FLASH message **/ |
70 | -#flash-container { | |
71 | - height: 50px; | |
72 | - position: fixed; | |
73 | - z-index: 10001; | |
74 | - top: 0px; | |
75 | - width: 100%; | |
76 | - margin-bottom: 15px; | |
77 | - overflow: hidden; | |
78 | - background: white; | |
79 | - cursor: pointer; | |
80 | - border-bottom: 1px solid #ccc; | |
81 | - text-align: center; | |
70 | +.flash-container { | |
82 | 71 | display: none; |
72 | + .alert { | |
73 | + cursor: pointer; | |
74 | + margin: 0; | |
75 | + text-align: center; | |
76 | + border-radius: 0; | |
83 | 77 | |
84 | - h4 { | |
85 | - color: #666; | |
86 | - font-size: 18px; | |
87 | - line-height: 38px; | |
88 | - padding-top: 5px; | |
89 | - margin: 2px; | |
90 | - font-weight: normal; | |
78 | + span { | |
79 | + font-size: 14px; | |
80 | + } | |
91 | 81 | } |
92 | 82 | } |
93 | 83 | |
... | ... | @@ -203,10 +193,6 @@ input[type=text] { |
203 | 193 | } |
204 | 194 | } |
205 | 195 | |
206 | -input.git_clone_url { | |
207 | - width: 325px; | |
208 | -} | |
209 | - | |
210 | 196 | .merge-request-form-holder { |
211 | 197 | select { |
212 | 198 | width: 300px; | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/common.scss
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
app/assets/stylesheets/gitlab_bootstrap/mixins.scss
... | ... | @@ -70,8 +70,19 @@ |
70 | 70 | @mixin header-font { |
71 | 71 | color: $style_color; |
72 | 72 | text-shadow: 0 1px 1px #FFF; |
73 | - font-family: 'Yanone', sans-serif; | |
74 | - font-size: 24px; | |
75 | - line-height: 36px; | |
73 | + font-size: 18px; | |
74 | + line-height: 42px; | |
76 | 75 | font-weight: normal; |
76 | + letter-spacing: -1px; | |
77 | +} | |
78 | + | |
79 | +@mixin md-typography { | |
80 | + code { padding: 0 4px; } | |
81 | + p { font-size: 13px; } | |
82 | + h1 { font-size: 26px; line-height: 40px; margin: 10px 0;} | |
83 | + h2 { font-size: 22px; line-height: 40px; margin: 10px 0;} | |
84 | + h3 { font-size: 18px; line-height: 40px; margin: 10px 0;} | |
85 | + h4 { font-size: 16px; line-height: 20px; margin: 10px 0;} | |
86 | + h5 { font-size: 14px; line-height: 20px; margin: 10px 0;} | |
87 | + h6 { font-size: 12px; line-height: 20px; margin: 10px 0;} | |
77 | 88 | } | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/typography.scss
... | ... | @@ -87,16 +87,15 @@ a:focus { |
87 | 87 | * |
88 | 88 | */ |
89 | 89 | .wiki { |
90 | + @include md-typography; | |
91 | + | |
90 | 92 | font-size: 13px; |
93 | + line-height: 20px; | |
91 | 94 | |
92 | - code { padding: 0 4px; } | |
93 | - p { font-size: 13px; } | |
94 | - h1 { font-size: 32px; line-height: 40px; margin: 10px 0;} | |
95 | - h2 { font-size: 26px; line-height: 40px; margin: 10px 0;} | |
96 | - h3 { font-size: 22px; line-height: 40px; margin: 10px 0;} | |
97 | - h4 { font-size: 18px; line-height: 20px; margin: 10px 0;} | |
98 | - h5 { font-size: 14px; line-height: 20px; margin: 10px 0;} | |
99 | - h6 { font-size: 12px; line-height: 20px; margin: 10px 0;} | |
100 | 95 | .white .highlight pre { background: #f5f5f5; } |
101 | 96 | ul { margin: 0 0 9px 25px !important; } |
102 | 97 | } |
98 | + | |
99 | +.md { | |
100 | + @include md-typography; | |
101 | +} | ... | ... |
app/assets/stylesheets/highlight/dark.scss
app/assets/stylesheets/sections/commits.scss
app/assets/stylesheets/sections/events.scss
... | ... | @@ -48,15 +48,13 @@ |
48 | 48 | color: #666; |
49 | 49 | } |
50 | 50 | .event-note { |
51 | - padding-top: 5px; | |
52 | - padding-left: 5px; | |
53 | - display: inline-block; | |
54 | 51 | color: #555; |
52 | + margin-top: 5px; | |
53 | + margin-left: 40px; | |
55 | 54 | |
56 | 55 | .note-file-attach { |
57 | - margin-left: -25px; | |
58 | - float: left; | |
59 | 56 | .note-image-attach { |
57 | + margin-top: 4px; | |
60 | 58 | margin-left: 0px; |
61 | 59 | max-width: 200px; |
62 | 60 | } |
... | ... | @@ -66,8 +64,8 @@ |
66 | 64 | color: #777; |
67 | 65 | float: left; |
68 | 66 | font-size: 16px; |
69 | - line-height: 18px; | |
70 | - margin: 5px; | |
67 | + line-height: 16px; | |
68 | + margin-right: 5px; | |
71 | 69 | } |
72 | 70 | } |
73 | 71 | .avatar { | ... | ... |
app/assets/stylesheets/sections/header.scss
app/assets/stylesheets/sections/login.scss
app/assets/stylesheets/sections/notes.scss
app/assets/stylesheets/sections/projects.scss
app/contexts/issues_list_context.rb
... | ... | @@ -7,12 +7,13 @@ class IssuesListContext < BaseContext |
7 | 7 | @issues = case params[:status] |
8 | 8 | when issues_filter[:all] then @project.issues |
9 | 9 | when issues_filter[:closed] then @project.issues.closed |
10 | - when issues_filter[:to_me] then @project.issues.opened.assigned(current_user) | |
10 | + when issues_filter[:to_me] then @project.issues.assigned(current_user) | |
11 | + when issues_filter[:by_me] then @project.issues.authored(current_user) | |
11 | 12 | else @project.issues.opened |
12 | 13 | end |
13 | 14 | |
14 | 15 | @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present? |
15 | - @issues = @issues.includes(:author, :project).order("updated_at") | |
16 | + @issues = @issues.includes(:author, :project) | |
16 | 17 | |
17 | 18 | # Filter by specific assignee_id (or lack thereof)? |
18 | 19 | if params[:assignee_id].present? | ... | ... |
app/controllers/admin/teams/members_controller.rb
app/controllers/admin/users_controller.rb
... | ... | @@ -45,7 +45,7 @@ class Admin::UsersController < Admin::ApplicationController |
45 | 45 | end |
46 | 46 | |
47 | 47 | def unblock |
48 | - if admin_user.update_attribute(:blocked, false) | |
48 | + if admin_user.activate | |
49 | 49 | redirect_to :back, alert: "Successfully unblocked" |
50 | 50 | else |
51 | 51 | redirect_to :back, alert: "Error occured. User was not unblocked" | ... | ... |
app/controllers/application_controller.rb
... | ... | @@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base |
5 | 5 | before_filter :add_abilities |
6 | 6 | before_filter :dev_tools if Rails.env == 'development' |
7 | 7 | before_filter :default_headers |
8 | + before_filter :add_gon_variables | |
8 | 9 | |
9 | 10 | protect_from_forgery |
10 | 11 | |
... | ... | @@ -29,7 +30,7 @@ class ApplicationController < ActionController::Base |
29 | 30 | end |
30 | 31 | |
31 | 32 | def reject_blocked! |
32 | - if current_user && current_user.blocked | |
33 | + if current_user && current_user.blocked? | |
33 | 34 | sign_out current_user |
34 | 35 | flash[:alert] = "Your account is blocked. Retry when an admin unblock it." |
35 | 36 | redirect_to new_user_session_path |
... | ... | @@ -37,7 +38,7 @@ class ApplicationController < ActionController::Base |
37 | 38 | end |
38 | 39 | |
39 | 40 | def after_sign_in_path_for resource |
40 | - if resource.is_a?(User) && resource.respond_to?(:blocked) && resource.blocked | |
41 | + if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked? | |
41 | 42 | sign_out resource |
42 | 43 | flash[:alert] = "Your account is blocked. Retry when an admin unblock it." |
43 | 44 | new_user_session_path |
... | ... | @@ -148,4 +149,8 @@ class ApplicationController < ActionController::Base |
148 | 149 | headers['X-Frame-Options'] = 'DENY' |
149 | 150 | headers['X-XSS-Protection'] = '1; mode=block' |
150 | 151 | end |
152 | + | |
153 | + def add_gon_variables | |
154 | + gon.default_issues_tracker = Project.issues_tracker.default_value | |
155 | + end | |
151 | 156 | end | ... | ... |
app/controllers/commits_controller.rb
... | ... | @@ -13,7 +13,7 @@ class CommitsController < ProjectResourceController |
13 | 13 | @limit, @offset = (params[:limit] || 40), (params[:offset] || 0) |
14 | 14 | |
15 | 15 | @commits = @repo.commits(@ref, @path, @limit, @offset) |
16 | - @commits = CommitDecorator.decorate(@commits) | |
16 | + @commits = CommitDecorator.decorate_collection(@commits) | |
17 | 17 | |
18 | 18 | respond_to do |format| |
19 | 19 | format.html # index.html.erb | ... | ... |
app/controllers/compare_controller.rb
... | ... | @@ -16,7 +16,7 @@ class CompareController < ProjectResourceController |
16 | 16 | @refs_are_same = result[:same] |
17 | 17 | @line_notes = [] |
18 | 18 | |
19 | - @commits = CommitDecorator.decorate(@commits) | |
19 | + @commits = CommitDecorator.decorate_collection(@commits) | |
20 | 20 | end |
21 | 21 | |
22 | 22 | def create | ... | ... |
app/controllers/graph_controller.rb
1 | 1 | class GraphController < ProjectResourceController |
2 | 2 | include ExtractsPath |
3 | + include ApplicationHelper | |
3 | 4 | |
4 | 5 | # Authorize |
5 | 6 | before_filter :authorize_read_project! |
... | ... | @@ -20,7 +21,10 @@ class GraphController < ProjectResourceController |
20 | 21 | respond_to do |format| |
21 | 22 | format.html |
22 | 23 | format.json do |
23 | - graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit) | |
24 | + graph = Graph::JsonBuilder.new(project, @ref, @commit) | |
25 | + graph.commits.each do |c| | |
26 | + c.icon = gravatar_icon(c.author.email) | |
27 | + end | |
24 | 28 | render :json => graph.to_json |
25 | 29 | end |
26 | 30 | end | ... | ... |
app/controllers/merge_requests_controller.rb
... | ... | @@ -81,7 +81,8 @@ class MergeRequestsController < ProjectResourceController |
81 | 81 | end |
82 | 82 | |
83 | 83 | def automerge |
84 | - return access_denied! unless can?(current_user, :accept_mr, @project) | |
84 | + return access_denied! unless allowed_to_merge? | |
85 | + | |
85 | 86 | if @merge_request.opened? && @merge_request.can_be_merged? |
86 | 87 | @merge_request.should_remove_source_branch = params[:should_remove_source_branch] |
87 | 88 | @merge_request.automerge!(current_user) |
... | ... | @@ -142,6 +143,19 @@ class MergeRequestsController < ProjectResourceController |
142 | 143 | # Get commits from repository |
143 | 144 | # or from cache if already merged |
144 | 145 | @commits = @merge_request.commits |
145 | - @commits = CommitDecorator.decorate(@commits) | |
146 | + @commits = CommitDecorator.decorate_collection(@commits) | |
147 | + | |
148 | + @allowed_to_merge = allowed_to_merge? | |
149 | + @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge | |
150 | + end | |
151 | + | |
152 | + def allowed_to_merge? | |
153 | + action = if project.protected_branch?(@merge_request.target_branch) | |
154 | + :push_code_to_protected_branches | |
155 | + else | |
156 | + :push_code | |
157 | + end | |
158 | + | |
159 | + can?(current_user, action, @project) | |
146 | 160 | end |
147 | 161 | end | ... | ... |
app/controllers/milestones_controller.rb
... | ... | @@ -32,7 +32,7 @@ class MilestonesController < ProjectResourceController |
32 | 32 | |
33 | 33 | def show |
34 | 34 | @issues = @milestone.issues |
35 | - @users = UserDecorator.decorate(@milestone.participants) | |
35 | + @users = UserDecorator.decorate_collection(@milestone.participants) | |
36 | 36 | @merge_requests = @milestone.merge_requests |
37 | 37 | |
38 | 38 | respond_to do |format| | ... | ... |
app/controllers/projects_controller.rb
app/controllers/teams/members_controller.rb
app/decorators/application_decorator.rb
1 | -class ApplicationDecorator < Draper::Base | |
1 | +class ApplicationDecorator < Draper::Decorator | |
2 | + delegate_all | |
2 | 3 | # Lazy Helpers |
3 | 4 | # PRO: Call Rails helpers without the h. proxy |
4 | 5 | # ex: number_to_currency(model.price) |
5 | 6 | # CON: Add a bazillion methods into your decorator's namespace |
6 | 7 | # and probably sacrifice performance/memory |
7 | - # | |
8 | + # | |
8 | 9 | # Enable them by uncommenting this line: |
9 | 10 | # lazy_helpers |
10 | 11 | |
11 | 12 | # Shared Decorations |
12 | 13 | # Consider defining shared methods common to all your models. |
13 | - # | |
14 | + # | |
14 | 15 | # Example: standardize the formatting of timestamps |
15 | 16 | # |
16 | 17 | # def formatted_timestamp(time) |
17 | - # h.content_tag :span, time.strftime("%a %m/%d/%y"), | |
18 | - # class: 'timestamp' | |
18 | + # h.content_tag :span, time.strftime("%a %m/%d/%y"), | |
19 | + # class: 'timestamp' | |
19 | 20 | # end |
20 | - # | |
21 | + # | |
21 | 22 | # def created_at |
22 | 23 | # formatted_timestamp(model.created_at) |
23 | 24 | # end |
24 | - # | |
25 | + # | |
25 | 26 | # def updated_at |
26 | 27 | # formatted_timestamp(model.updated_at) |
27 | 28 | # end | ... | ... |
app/helpers/application_helper.rb
... | ... | @@ -164,7 +164,8 @@ module ApplicationHelper |
164 | 164 | end |
165 | 165 | |
166 | 166 | def image_url(source) |
167 | - root_url + path_to_image(source) | |
167 | + # prevent relative_root_path being added twice (it's part of root_url and path_to_image) | |
168 | + root_url.sub(/#{root_path}$/, path_to_image(source)) | |
168 | 169 | end |
169 | 170 | |
170 | 171 | alias_method :url_to_image, :image_url | ... | ... |
app/helpers/issues_helper.rb
... | ... | @@ -27,6 +27,7 @@ module IssuesHelper |
27 | 27 | all: "all", |
28 | 28 | closed: "closed", |
29 | 29 | to_me: "assigned-to-me", |
30 | + by_me: "created-by-me", | |
30 | 31 | open: "open" |
31 | 32 | } |
32 | 33 | end |
... | ... | @@ -40,4 +41,39 @@ module IssuesHelper |
40 | 41 | def issues_active_milestones |
41 | 42 | @project.milestones.active.order("id desc").all |
42 | 43 | end |
44 | + | |
45 | + def url_for_project_issues | |
46 | + return "" if @project.nil? | |
47 | + | |
48 | + if @project.used_default_issues_tracker? | |
49 | + project_issues_filter_path(@project) | |
50 | + else | |
51 | + url = Settings[:issues_tracker][@project.issues_tracker]["project_url"] | |
52 | + url.gsub(':project_id', @project.id.to_s) | |
53 | + .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) | |
54 | + end | |
55 | + end | |
56 | + | |
57 | + def url_for_issue(issue_id) | |
58 | + return "" if @project.nil? | |
59 | + | |
60 | + if @project.used_default_issues_tracker? | |
61 | + url = project_issue_url project_id: @project, id: issue_id | |
62 | + else | |
63 | + url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"] | |
64 | + url.gsub(':id', issue_id.to_s) | |
65 | + .gsub(':project_id', @project.id.to_s) | |
66 | + .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) | |
67 | + end | |
68 | + end | |
69 | + | |
70 | + def title_for_issue(issue_id) | |
71 | + return "" if @project.nil? | |
72 | + | |
73 | + if @project.used_default_issues_tracker? && issue = @project.issues.where(id: issue_id).first | |
74 | + issue.title | |
75 | + else | |
76 | + "" | |
77 | + end | |
78 | + end | |
43 | 79 | end | ... | ... |
app/helpers/tree_helper.rb
... | ... | @@ -13,13 +13,15 @@ module TreeHelper |
13 | 13 | tree += render partial: 'tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present? |
14 | 14 | |
15 | 15 | files.each do |f| |
16 | - if f.respond_to?(:url) | |
17 | - # Object is a Submodule | |
18 | - tree += render partial: 'tree/submodule_item', object: f | |
19 | - else | |
20 | - # Object is a Blob | |
21 | - tree += render partial: 'tree/tree_item', object: f, locals: {type: 'file'} | |
22 | - end | |
16 | + html = if f.respond_to?(:url) | |
17 | + # Object is a Submodule | |
18 | + render partial: 'tree/submodule_item', object: f | |
19 | + else | |
20 | + # Object is a Blob | |
21 | + render partial: 'tree/tree_item', object: f, locals: {type: 'file'} | |
22 | + end | |
23 | + | |
24 | + tree += html if html.present? | |
23 | 25 | end |
24 | 26 | |
25 | 27 | tree.html_safe | ... | ... |
app/models/ability.rb
... | ... | @@ -0,0 +1,59 @@ |
1 | +require "grit" | |
2 | + | |
3 | +module Graph | |
4 | + class Commit | |
5 | + include ActionView::Helpers::TagHelper | |
6 | + | |
7 | + attr_accessor :time, :spaces, :refs, :parent_spaces, :icon | |
8 | + | |
9 | + def initialize(commit) | |
10 | + @_commit = commit | |
11 | + @time = -1 | |
12 | + @spaces = [] | |
13 | + @parent_spaces = [] | |
14 | + end | |
15 | + | |
16 | + def method_missing(m, *args, &block) | |
17 | + @_commit.send(m, *args, &block) | |
18 | + end | |
19 | + | |
20 | + def to_graph_hash | |
21 | + h = {} | |
22 | + h[:parents] = self.parents.collect do |p| | |
23 | + [p.id,0,0] | |
24 | + end | |
25 | + h[:author] = { | |
26 | + name: author.name, | |
27 | + email: author.email, | |
28 | + icon: icon | |
29 | + } | |
30 | + h[:time] = time | |
31 | + h[:space] = spaces.first | |
32 | + h[:parent_spaces] = parent_spaces | |
33 | + h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? | |
34 | + h[:id] = sha | |
35 | + h[:date] = date | |
36 | + h[:message] = message | |
37 | + h | |
38 | + end | |
39 | + | |
40 | + def add_refs(ref_cache, repo) | |
41 | + if ref_cache.empty? | |
42 | + repo.refs.each do |ref| | |
43 | + ref_cache[ref.commit.id] ||= [] | |
44 | + ref_cache[ref.commit.id] << ref | |
45 | + end | |
46 | + end | |
47 | + @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id) | |
48 | + @refs ||= [] | |
49 | + end | |
50 | + | |
51 | + def space | |
52 | + if @spaces.size > 0 | |
53 | + @spaces.first | |
54 | + else | |
55 | + 0 | |
56 | + end | |
57 | + end | |
58 | + end | |
59 | +end | ... | ... |
... | ... | @@ -0,0 +1,291 @@ |
1 | +require "grit" | |
2 | + | |
3 | +module Graph | |
4 | + class JsonBuilder | |
5 | + attr_accessor :days, :commits, :ref_cache, :repo | |
6 | + | |
7 | + def self.max_count | |
8 | + @max_count ||= 650 | |
9 | + end | |
10 | + | |
11 | + def initialize project, ref, commit | |
12 | + @project = project | |
13 | + @ref = ref | |
14 | + @commit = commit | |
15 | + @repo = project.repo | |
16 | + @ref_cache = {} | |
17 | + | |
18 | + @commits = collect_commits | |
19 | + @days = index_commits | |
20 | + end | |
21 | + | |
22 | + def to_json(*args) | |
23 | + { | |
24 | + days: @days.compact.map { |d| [d.day, d.strftime("%b")] }, | |
25 | + commits: @commits.map(&:to_graph_hash) | |
26 | + }.to_json(*args) | |
27 | + end | |
28 | + | |
29 | + protected | |
30 | + | |
31 | + # Get commits from repository | |
32 | + # | |
33 | + def collect_commits | |
34 | + | |
35 | + @commits = Grit::Commit.find_all(repo, nil, {date_order: true, max_count: self.class.max_count, skip: to_commit}).dup | |
36 | + | |
37 | + # Decorate with app/models/commit.rb | |
38 | + @commits.map! { |commit| Commit.new(commit) } | |
39 | + | |
40 | + # Decorate with lib/gitlab/graph/commit.rb | |
41 | + @commits.map! { |commit| Graph::Commit.new(commit) } | |
42 | + | |
43 | + # add refs to each commit | |
44 | + @commits.each { |commit| commit.add_refs(ref_cache, repo) } | |
45 | + | |
46 | + @commits | |
47 | + end | |
48 | + | |
49 | + # Method is adding time and space on the | |
50 | + # list of commits. As well as returns date list | |
51 | + # corelated with time set on commits. | |
52 | + # | |
53 | + # @param [Array<Graph::Commit>] commits to index | |
54 | + # | |
55 | + # @return [Array<TimeDate>] list of commit dates corelated with time on commits | |
56 | + def index_commits | |
57 | + days, times = [], [] | |
58 | + map = {} | |
59 | + | |
60 | + commits.reverse.each_with_index do |c,i| | |
61 | + c.time = i | |
62 | + days[i] = c.committed_date | |
63 | + map[c.id] = c | |
64 | + times[i] = c | |
65 | + end | |
66 | + | |
67 | + @_reserved = {} | |
68 | + days.each_index do |i| | |
69 | + @_reserved[i] = [] | |
70 | + end | |
71 | + | |
72 | + commits_sort_by_ref.each do |commit| | |
73 | + if map.include? commit.id then | |
74 | + place_chain(map[commit.id], map) | |
75 | + end | |
76 | + end | |
77 | + | |
78 | + # find parent spaces for not overlap lines | |
79 | + times.each do |c| | |
80 | + c.parent_spaces.concat(find_free_parent_spaces(c, map, times)) | |
81 | + end | |
82 | + | |
83 | + days | |
84 | + end | |
85 | + | |
86 | + # Skip count that the target commit is displayed in center. | |
87 | + def to_commit | |
88 | + commits = Grit::Commit.find_all(repo, nil, {date_order: true}) | |
89 | + commit_index = commits.index do |c| | |
90 | + c.id == @commit.id | |
91 | + end | |
92 | + | |
93 | + if commit_index && (self.class.max_count / 2 < commit_index) then | |
94 | + # get max index that commit is displayed in the center. | |
95 | + commit_index - self.class.max_count / 2 | |
96 | + else | |
97 | + 0 | |
98 | + end | |
99 | + end | |
100 | + | |
101 | + def commits_sort_by_ref | |
102 | + commits.sort do |a,b| | |
103 | + if include_ref?(a) | |
104 | + -1 | |
105 | + elsif include_ref?(b) | |
106 | + 1 | |
107 | + else | |
108 | + b.committed_date <=> a.committed_date | |
109 | + end | |
110 | + end | |
111 | + end | |
112 | + | |
113 | + def include_ref?(commit) | |
114 | + heads = commit.refs.select do |ref| | |
115 | + ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag) | |
116 | + end | |
117 | + | |
118 | + heads.map! do |head| | |
119 | + head.name | |
120 | + end | |
121 | + | |
122 | + heads.include?(@ref) | |
123 | + end | |
124 | + | |
125 | + def find_free_parent_spaces(commit, map, times) | |
126 | + spaces = [] | |
127 | + | |
128 | + commit.parents.each do |p| | |
129 | + if map.include?(p.id) then | |
130 | + parent = map[p.id] | |
131 | + | |
132 | + range = if commit.time < parent.time then | |
133 | + commit.time..parent.time | |
134 | + else | |
135 | + parent.time..commit.time | |
136 | + end | |
137 | + | |
138 | + space = if commit.space >= parent.space then | |
139 | + find_free_parent_space(range, parent.space, -1, commit.space, times) | |
140 | + else | |
141 | + find_free_parent_space(range, commit.space, -1, parent.space, times) | |
142 | + end | |
143 | + | |
144 | + mark_reserved(range, space) | |
145 | + spaces << space | |
146 | + end | |
147 | + end | |
148 | + | |
149 | + spaces | |
150 | + end | |
151 | + | |
152 | + def find_free_parent_space(range, space_base, space_step, space_default, times) | |
153 | + if is_overlap?(range, times, space_default) then | |
154 | + find_free_space(range, space_step, space_base, space_default) | |
155 | + else | |
156 | + space_default | |
157 | + end | |
158 | + end | |
159 | + | |
160 | + def is_overlap?(range, times, overlap_space) | |
161 | + range.each do |i| | |
162 | + if i != range.first && | |
163 | + i != range.last && | |
164 | + times[i].spaces.include?(overlap_space) then | |
165 | + | |
166 | + return true; | |
167 | + end | |
168 | + end | |
169 | + | |
170 | + false | |
171 | + end | |
172 | + | |
173 | + # Add space mark on commit and its parents | |
174 | + # | |
175 | + # @param [Graph::Commit] the commit object. | |
176 | + # @param [Hash<String,Graph::Commit>] map of commits | |
177 | + def place_chain(commit, map, parent_time = nil) | |
178 | + leaves = take_left_leaves(commit, map) | |
179 | + if leaves.empty? | |
180 | + return | |
181 | + end | |
182 | + | |
183 | + time_range = leaves.last.time..leaves.first.time | |
184 | + space_base = get_space_base(leaves, map) | |
185 | + space = find_free_space(time_range, 2, space_base) | |
186 | + leaves.each do |l| | |
187 | + l.spaces << space | |
188 | + # Also add space to parent | |
189 | + l.parents.each do |p| | |
190 | + if map.include?(p.id) | |
191 | + parent = map[p.id] | |
192 | + if parent.space > 0 | |
193 | + parent.spaces << space | |
194 | + end | |
195 | + end | |
196 | + end | |
197 | + end | |
198 | + | |
199 | + # and mark it as reserved | |
200 | + min_time = leaves.last.time | |
201 | + parents = leaves.last.parents.collect | |
202 | + parents.each do |p| | |
203 | + if map.include? p.id | |
204 | + parent = map[p.id] | |
205 | + if parent.time < min_time | |
206 | + min_time = parent.time | |
207 | + end | |
208 | + end | |
209 | + end | |
210 | + | |
211 | + if parent_time.nil? | |
212 | + max_time = leaves.first.time | |
213 | + else | |
214 | + max_time = parent_time - 1 | |
215 | + end | |
216 | + mark_reserved(min_time..max_time, space) | |
217 | + | |
218 | + # Visit branching chains | |
219 | + leaves.each do |l| | |
220 | + parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?} | |
221 | + for p in parents | |
222 | + place_chain(map[p.id], map, l.time) | |
223 | + end | |
224 | + end | |
225 | + end | |
226 | + | |
227 | + def get_space_base(leaves, map) | |
228 | + space_base = 1 | |
229 | + if leaves.last.parents.size > 0 | |
230 | + first_parent = leaves.last.parents.first | |
231 | + if map.include?(first_parent.id) | |
232 | + first_p = map[first_parent.id] | |
233 | + if first_p.space > 0 | |
234 | + space_base = first_p.space | |
235 | + end | |
236 | + end | |
237 | + end | |
238 | + space_base | |
239 | + end | |
240 | + | |
241 | + def mark_reserved(time_range, space) | |
242 | + for day in time_range | |
243 | + @_reserved[day].push(space) | |
244 | + end | |
245 | + end | |
246 | + | |
247 | + def find_free_space(time_range, space_step, space_base = 1, space_default = nil) | |
248 | + space_default ||= space_base | |
249 | + | |
250 | + reserved = [] | |
251 | + for day in time_range | |
252 | + reserved += @_reserved[day] | |
253 | + end | |
254 | + reserved.uniq! | |
255 | + | |
256 | + space = space_default | |
257 | + while reserved.include?(space) do | |
258 | + space += space_step | |
259 | + if space < space_base then | |
260 | + space_step *= -1 | |
261 | + space = space_base + space_step | |
262 | + end | |
263 | + end | |
264 | + | |
265 | + space | |
266 | + end | |
267 | + | |
268 | + # Takes most left subtree branch of commits | |
269 | + # which don't have space mark yet. | |
270 | + # | |
271 | + # @param [Graph::Commit] the commit object. | |
272 | + # @param [Hash<String,Graph::Commit>] map of commits | |
273 | + # | |
274 | + # @return [Array<Graph::Commit>] list of branch commits | |
275 | + def take_left_leaves(commit, map) | |
276 | + leaves = [] | |
277 | + leaves.push(commit) if commit.space.zero? | |
278 | + | |
279 | + while true | |
280 | + return leaves if commit.parents.count.zero? | |
281 | + return leaves unless map.include? commit.parents.first.id | |
282 | + | |
283 | + commit = map[commit.parents.first.id] | |
284 | + | |
285 | + return leaves unless commit.space.zero? | |
286 | + | |
287 | + leaves.push(commit) | |
288 | + end | |
289 | + end | |
290 | + end | |
291 | +end | ... | ... |
app/models/group.rb
... | ... | @@ -2,13 +2,14 @@ |
2 | 2 | # |
3 | 3 | # Table name: namespaces |
4 | 4 | # |
5 | -# id :integer not null, primary key | |
6 | -# name :string(255) not null | |
7 | -# path :string(255) not null | |
8 | -# owner_id :integer not null | |
9 | -# created_at :datetime not null | |
10 | -# updated_at :datetime not null | |
11 | -# type :string(255) | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) not null | |
7 | +# description :string(255) not null | |
8 | +# path :string(255) not null | |
9 | +# owner_id :integer not null | |
10 | +# created_at :datetime not null | |
11 | +# updated_at :datetime not null | |
12 | +# type :string(255) | |
12 | 13 | # |
13 | 14 | |
14 | 15 | class Group < Namespace | ... | ... |
app/models/issue.rb
app/models/namespace.rb
... | ... | @@ -2,17 +2,18 @@ |
2 | 2 | # |
3 | 3 | # Table name: namespaces |
4 | 4 | # |
5 | -# id :integer not null, primary key | |
6 | -# name :string(255) not null | |
7 | -# path :string(255) not null | |
8 | -# owner_id :integer not null | |
9 | -# created_at :datetime not null | |
10 | -# updated_at :datetime not null | |
11 | -# type :string(255) | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) not null | |
7 | +# description :string(255) not null | |
8 | +# path :string(255) not null | |
9 | +# owner_id :integer not null | |
10 | +# created_at :datetime not null | |
11 | +# updated_at :datetime not null | |
12 | +# type :string(255) | |
12 | 13 | # |
13 | 14 | |
14 | 15 | class Namespace < ActiveRecord::Base |
15 | - attr_accessible :name, :path | |
16 | + attr_accessible :name, :description, :path | |
16 | 17 | |
17 | 18 | has_many :projects, dependent: :destroy |
18 | 19 | belongs_to :owner, class_name: "User" |
... | ... | @@ -22,7 +23,7 @@ class Namespace < ActiveRecord::Base |
22 | 23 | length: { within: 0..255 }, |
23 | 24 | format: { with: Gitlab::Regex.name_regex, |
24 | 25 | message: "only letters, digits, spaces & '_' '-' '.' allowed." } |
25 | - | |
26 | + validates :description, length: { within: 0..255 } | |
26 | 27 | validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, |
27 | 28 | format: { with: Gitlab::Regex.path_regex, |
28 | 29 | message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } | ... | ... |
app/models/project.rb
... | ... | @@ -11,6 +11,7 @@ |
11 | 11 | # creator_id :integer |
12 | 12 | # default_branch :string(255) |
13 | 13 | # issues_enabled :boolean default(TRUE), not null |
14 | +# issues_tracker :string not null | |
14 | 15 | # wall_enabled :boolean default(TRUE), not null |
15 | 16 | # merge_requests_enabled :boolean default(TRUE), not null |
16 | 17 | # wiki_enabled :boolean default(TRUE), not null |
... | ... | @@ -22,11 +23,12 @@ require "grit" |
22 | 23 | |
23 | 24 | class Project < ActiveRecord::Base |
24 | 25 | include Gitolited |
26 | + extend Enumerize | |
25 | 27 | |
26 | 28 | class TransferError < StandardError; end |
27 | 29 | |
28 | - attr_accessible :name, :path, :description, :default_branch, | |
29 | - :issues_enabled, :wall_enabled, :merge_requests_enabled, | |
30 | + attr_accessible :name, :path, :description, :default_branch, :issues_tracker, | |
31 | + :issues_enabled, :wall_enabled, :merge_requests_enabled, :issues_tracker_id, | |
30 | 32 | :wiki_enabled, :public, :import_url, as: [:default, :admin] |
31 | 33 | |
32 | 34 | attr_accessible :namespace_id, :creator_id, as: :admin |
... | ... | @@ -43,7 +45,7 @@ class Project < ActiveRecord::Base |
43 | 45 | |
44 | 46 | has_many :events, dependent: :destroy |
45 | 47 | has_many :merge_requests, dependent: :destroy |
46 | - has_many :issues, dependent: :destroy, order: "state, created_at DESC" | |
48 | + has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC" | |
47 | 49 | has_many :milestones, dependent: :destroy |
48 | 50 | has_many :users_projects, dependent: :destroy |
49 | 51 | has_many :notes, dependent: :destroy |
... | ... | @@ -72,6 +74,7 @@ class Project < ActiveRecord::Base |
72 | 74 | message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } |
73 | 75 | validates :issues_enabled, :wall_enabled, :merge_requests_enabled, |
74 | 76 | :wiki_enabled, inclusion: { in: [true, false] } |
77 | + validates :issues_tracker_id, length: { within: 0..255 } | |
75 | 78 | |
76 | 79 | validates_uniqueness_of :name, scope: :namespace_id |
77 | 80 | validates_uniqueness_of :path, scope: :namespace_id |
... | ... | @@ -93,6 +96,8 @@ class Project < ActiveRecord::Base |
93 | 96 | scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } |
94 | 97 | scope :public_only, -> { where(public: true) } |
95 | 98 | |
99 | + enumerize :issues_tracker, :in => (Gitlab.config.issues_tracker.keys).append(:gitlab), :default => :gitlab | |
100 | + | |
96 | 101 | class << self |
97 | 102 | def abandoned |
98 | 103 | project_ids = Event.select('max(created_at) as latest_date, project_id'). |
... | ... | @@ -201,6 +206,22 @@ class Project < ActiveRecord::Base |
201 | 206 | issues.tag_counts_on(:labels) |
202 | 207 | end |
203 | 208 | |
209 | + def issue_exists?(issue_id) | |
210 | + if used_default_issues_tracker? | |
211 | + self.issues.where(id: issue_id).first.present? | |
212 | + else | |
213 | + true | |
214 | + end | |
215 | + end | |
216 | + | |
217 | + def used_default_issues_tracker? | |
218 | + self.issues_tracker == Project.issues_tracker.default_value | |
219 | + end | |
220 | + | |
221 | + def can_have_issues_tracker_id? | |
222 | + self.issues_enabled && !self.used_default_issues_tracker? | |
223 | + end | |
224 | + | |
204 | 225 | def services |
205 | 226 | [gitlab_ci_service].compact |
206 | 227 | end | ... | ... |
app/models/repository.rb
... | ... | @@ -137,7 +137,7 @@ class Repository |
137 | 137 | file_path = File.join(storage_path, self.path_with_namespace, file_name) |
138 | 138 | |
139 | 139 | # Put files into a directory before archiving |
140 | - prefix = self.path_with_namespace + "/" | |
140 | + prefix = File.basename(self.path_with_namespace) + "/" | |
141 | 141 | |
142 | 142 | # Create file if not exists |
143 | 143 | unless File.exists?(file_path) | ... | ... |
app/models/user.rb
... | ... | @@ -25,7 +25,7 @@ |
25 | 25 | # dark_scheme :boolean default(FALSE), not null |
26 | 26 | # theme_id :integer default(1), not null |
27 | 27 | # bio :string(255) |
28 | -# blocked :boolean default(FALSE), not null | |
28 | +# state :string(255) | |
29 | 29 | # failed_attempts :integer default(0) |
30 | 30 | # locked_at :datetime |
31 | 31 | # extern_uid :string(255) |
... | ... | @@ -46,10 +46,35 @@ class User < ActiveRecord::Base |
46 | 46 | |
47 | 47 | attr_accessor :force_random_password |
48 | 48 | |
49 | + # | |
50 | + # Relations | |
51 | + # | |
52 | + | |
49 | 53 | # Namespace for personal projects |
50 | - has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' | |
54 | + has_one :namespace, | |
55 | + dependent: :destroy, | |
56 | + foreign_key: :owner_id, | |
57 | + class_name: "Namespace", | |
58 | + conditions: 'type IS NULL' | |
59 | + | |
60 | + # Profile | |
61 | + has_many :keys, dependent: :destroy | |
62 | + | |
63 | + # Groups | |
64 | + has_many :groups, class_name: "Group", foreign_key: :owner_id | |
65 | + | |
66 | + # Teams | |
67 | + has_many :own_teams, | |
68 | + class_name: "UserTeam", | |
69 | + foreign_key: :owner_id, | |
70 | + dependent: :destroy | |
71 | + | |
72 | + has_many :user_team_user_relationships, dependent: :destroy | |
73 | + has_many :user_teams, through: :user_team_user_relationships | |
74 | + has_many :user_team_project_relationships, through: :user_teams | |
75 | + has_many :team_projects, through: :user_team_project_relationships | |
51 | 76 | |
52 | - has_many :keys, dependent: :destroy | |
77 | + # Projects | |
53 | 78 | has_many :users_projects, dependent: :destroy |
54 | 79 | has_many :issues, dependent: :destroy, foreign_key: :author_id |
55 | 80 | has_many :notes, dependent: :destroy, foreign_key: :author_id |
... | ... | @@ -57,18 +82,16 @@ class User < ActiveRecord::Base |
57 | 82 | has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" |
58 | 83 | has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" |
59 | 84 | has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" |
85 | + has_many :projects, through: :users_projects | |
60 | 86 | |
61 | - has_many :groups, class_name: "Group", foreign_key: :owner_id | |
62 | - has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" | |
63 | - | |
64 | - has_many :projects, through: :users_projects | |
65 | - | |
66 | - has_many :user_team_user_relationships, dependent: :destroy | |
67 | - | |
68 | - has_many :user_teams, through: :user_team_user_relationships | |
69 | - has_many :user_team_project_relationships, through: :user_teams | |
70 | - has_many :team_projects, through: :user_team_project_relationships | |
87 | + has_many :recent_events, | |
88 | + class_name: "Event", | |
89 | + foreign_key: :author_id, | |
90 | + order: "id DESC" | |
71 | 91 | |
92 | + # | |
93 | + # Validations | |
94 | + # | |
72 | 95 | validates :name, presence: true |
73 | 96 | validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ } |
74 | 97 | validates :bio, length: { within: 0..255 } |
... | ... | @@ -87,10 +110,27 @@ class User < ActiveRecord::Base |
87 | 110 | |
88 | 111 | delegate :path, to: :namespace, allow_nil: true, prefix: true |
89 | 112 | |
113 | + state_machine :state, initial: :active do | |
114 | + after_transition any => :blocked do |user, transition| | |
115 | + # Remove user from all projects and | |
116 | + user.users_projects.find_each do |membership| | |
117 | + return false unless membership.destroy | |
118 | + end | |
119 | + end | |
120 | + | |
121 | + event :block do | |
122 | + transition active: :blocked | |
123 | + end | |
124 | + | |
125 | + event :activate do | |
126 | + transition blocked: :active | |
127 | + end | |
128 | + end | |
129 | + | |
90 | 130 | # Scopes |
91 | 131 | scope :admins, -> { where(admin: true) } |
92 | - scope :blocked, -> { where(blocked: true) } | |
93 | - scope :active, -> { where(blocked: false) } | |
132 | + scope :blocked, -> { with_state(:blocked) } | |
133 | + scope :active, -> { with_state(:active) } | |
94 | 134 | scope :alphabetically, -> { order('name ASC') } |
95 | 135 | scope :in_team, ->(team){ where(id: team.member_ids) } |
96 | 136 | scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } |
... | ... | @@ -260,17 +300,6 @@ class User < ActiveRecord::Base |
260 | 300 | MergeRequest.cared(self) |
261 | 301 | end |
262 | 302 | |
263 | - # Remove user from all projects and | |
264 | - # set blocked attribute to true | |
265 | - def block | |
266 | - users_projects.find_each do |membership| | |
267 | - return false unless membership.destroy | |
268 | - end | |
269 | - | |
270 | - self.blocked = true | |
271 | - save | |
272 | - end | |
273 | - | |
274 | 303 | def projects_limit_percent |
275 | 304 | return 100 if projects_limit.zero? |
276 | 305 | (personal_projects.count.to_f / projects_limit) * 100 | ... | ... |
app/models/user_team.rb
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | # |
12 | 12 | |
13 | 13 | class UserTeam < ActiveRecord::Base |
14 | - attr_accessible :name, :owner_id, :path | |
14 | + attr_accessible :name, :description, :owner_id, :path | |
15 | 15 | |
16 | 16 | belongs_to :owner, class_name: User |
17 | 17 | |
... | ... | @@ -26,6 +26,7 @@ class UserTeam < ActiveRecord::Base |
26 | 26 | length: { within: 0..255 }, |
27 | 27 | format: { with: Gitlab::Regex.name_regex, |
28 | 28 | message: "only letters, digits, spaces & '_' '-' '.' allowed." } |
29 | + validates :description, length: { within: 0..255 } | |
29 | 30 | validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, |
30 | 31 | format: { with: Gitlab::Regex.path_regex, |
31 | 32 | message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } | ... | ... |
app/observers/issue_observer.rb
... | ... | @@ -27,7 +27,7 @@ class IssueObserver < ActiveRecord::Observer |
27 | 27 | |
28 | 28 | def create_note(issue) |
29 | 29 | Note.create_status_change_note(issue, current_user, issue.state) |
30 | - [issue.author, issue.assignee].compact.each do |recipient| | |
30 | + [issue.author, issue.assignee].compact.uniq.each do |recipient| | |
31 | 31 | Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id) |
32 | 32 | end |
33 | 33 | end | ... | ... |
app/observers/user_observer.rb
... | ... | @@ -2,7 +2,8 @@ class UserObserver < ActiveRecord::Observer |
2 | 2 | def after_create(user) |
3 | 3 | log_info("User \"#{user.name}\" (#{user.email}) was created") |
4 | 4 | |
5 | - Notify.delay.new_user_email(user.id, user.password) | |
5 | + # Dont email omniauth created users | |
6 | + Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid? | |
6 | 7 | end |
7 | 8 | |
8 | 9 | def after_destroy user | ... | ... |
app/services/git_push_service.rb
... | ... | @@ -19,6 +19,8 @@ class GitPushService |
19 | 19 | # Collect data for this git push |
20 | 20 | @push_data = post_receive_data(oldrev, newrev, ref) |
21 | 21 | |
22 | + create_push_event | |
23 | + | |
22 | 24 | project.ensure_satellite_exists |
23 | 25 | project.discover_default_branch |
24 | 26 | |
... | ... | @@ -27,8 +29,6 @@ class GitPushService |
27 | 29 | project.execute_hooks(@push_data.dup) |
28 | 30 | project.execute_services(@push_data.dup) |
29 | 31 | end |
30 | - | |
31 | - create_push_event | |
32 | 32 | end |
33 | 33 | |
34 | 34 | # This method provide a sample data | ... | ... |
app/services/project_transfer_service.rb
app/views/admin/groups/edit.html.haml
1 | -%h3.page_title Rename Group | |
1 | +%h3.page_title Edit Group | |
2 | 2 | %hr |
3 | 3 | = form_for [:admin, @group] do |f| |
4 | 4 | - if @group.errors.any? |
... | ... | @@ -10,7 +10,10 @@ |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Example Group", class: "xxlarge" |
12 | 12 | |
13 | - | |
13 | + .clearfix.group-description-holder | |
14 | + = f.label :description, "Details" | |
15 | + .input | |
16 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
14 | 17 | |
15 | 18 | .clearfix.group_name_holder |
16 | 19 | = f.label :path do |
... | ... | @@ -24,5 +27,5 @@ |
24 | 27 | %li It will change the git path to repositories under this group. |
25 | 28 | |
26 | 29 | .form-actions |
27 | - = f.submit 'Rename group', class: "btn btn-remove" | |
30 | + = f.submit 'Edit group', class: "btn btn-remove" | |
28 | 31 | = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" | ... | ... |
app/views/admin/groups/index.html.haml
... | ... | @@ -17,6 +17,7 @@ |
17 | 17 | Name |
18 | 18 | %i.icon-sort-down |
19 | 19 | %th Path |
20 | + %th Description | |
20 | 21 | %th Projects |
21 | 22 | %th Owner |
22 | 23 | %th.cred Danger Zone! |
... | ... | @@ -25,11 +26,12 @@ |
25 | 26 | %tr |
26 | 27 | %td |
27 | 28 | %strong= link_to group.name, [:admin, group] |
29 | + %td= truncate group.description | |
28 | 30 | %td= group.path |
29 | 31 | %td= group.projects.count |
30 | 32 | %td |
31 | 33 | = link_to group.owner_name, admin_user_path(group.owner) |
32 | 34 | %td.bgred |
33 | - = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" | |
35 | + = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" | |
34 | 36 | = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" |
35 | 37 | = paginate @groups, theme: "admin" | ... | ... |
app/views/admin/groups/new.html.haml
... | ... | @@ -9,8 +9,14 @@ |
9 | 9 | Group name is |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" |
12 | - | |
13 | - = f.submit 'Create group', class: "btn btn-primary" | |
12 | + .clearfix.group-description-holder | |
13 | + = f.label :description, "Details" | |
14 | + .input | |
15 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
16 | + | |
17 | + .form-actions | |
18 | + = f.submit 'Create group', class: "btn btn-primary" | |
19 | + | |
14 | 20 | %hr |
15 | 21 | .padded |
16 | 22 | %ul | ... | ... |
app/views/admin/groups/show.html.haml
app/views/admin/projects/_form.html.haml
... | ... | @@ -31,6 +31,15 @@ |
31 | 31 | = f.label :issues_enabled, "Issues" |
32 | 32 | .input= f.check_box :issues_enabled |
33 | 33 | |
34 | + - if Project.issues_tracker.values.count > 1 | |
35 | + .clearfix | |
36 | + = f.label :issues_tracker, "Issues tracker", class: 'control-label' | |
37 | + .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) | |
38 | + | |
39 | + .clearfix | |
40 | + = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' | |
41 | + .input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id? | |
42 | + | |
34 | 43 | .clearfix |
35 | 44 | = f.label :merge_requests_enabled, "Merge Requests" |
36 | 45 | .input= f.check_box :merge_requests_enabled | ... | ... |
app/views/admin/teams/edit.html.haml
1 | -%h3.page_title Rename Team | |
1 | +%h3.page_title Edit Team | |
2 | 2 | %hr |
3 | 3 | = form_for @team, url: admin_team_path(@team), method: :put do |f| |
4 | 4 | - if @team.errors.any? |
... | ... | @@ -10,6 +10,11 @@ |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Example Team", class: "xxlarge" |
12 | 12 | |
13 | + .clearfix.team-description-holder | |
14 | + = f.label :description, "Details" | |
15 | + .input | |
16 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
17 | + | |
13 | 18 | .clearfix.team_name_holder |
14 | 19 | = f.label :path do |
15 | 20 | %span.cred Team path is |
... | ... | @@ -19,5 +24,5 @@ |
19 | 24 | %li It will change web url for access team and team projects. |
20 | 25 | |
21 | 26 | .form-actions |
22 | - = f.submit 'Rename team', class: "btn btn-remove" | |
27 | + = f.submit 'Edit team', class: "btn btn-remove" | |
23 | 28 | = link_to 'Cancel', admin_teams_path, class: "btn btn-cancel" | ... | ... |
app/views/admin/teams/index.html.haml
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | %th |
17 | 17 | Name |
18 | 18 | %i.icon-sort-down |
19 | + %th Description | |
19 | 20 | %th Path |
20 | 21 | %th Projects |
21 | 22 | %th Members |
... | ... | @@ -26,13 +27,17 @@ |
26 | 27 | %tr |
27 | 28 | %td |
28 | 29 | %strong= link_to team.name, admin_team_path(team) |
30 | + %td= truncate team.description | |
29 | 31 | %td= team.path |
30 | 32 | %td= team.projects.count |
31 | 33 | %td= team.members.count |
32 | 34 | %td |
33 | - = link_to team.owner.name, admin_user_path(team.owner) | |
35 | + - if team.owner | |
36 | + = link_to team.owner.name, admin_user_path(team.owner) | |
37 | + - else | |
38 | + (deleted) | |
34 | 39 | %td.bgred |
35 | - = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small" | |
40 | + = link_to 'Edit', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small" | |
36 | 41 | = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" |
37 | 42 | |
38 | 43 | = paginate @teams, theme: "admin" | ... | ... |
app/views/admin/teams/new.html.haml
... | ... | @@ -9,8 +9,15 @@ |
9 | 9 | Team name is |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" |
12 | - | |
13 | - = f.submit 'Create team', class: "btn btn-primary" | |
12 | + | |
13 | + .clearfix.team-description-holder | |
14 | + = f.label :description, "Details" | |
15 | + .input | |
16 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
17 | + | |
18 | + .form-actions | |
19 | + = f.submit 'Create team', class: "btn btn-primary" | |
20 | + | |
14 | 21 | %hr |
15 | 22 | .padded |
16 | 23 | %ul | ... | ... |
app/views/admin/teams/show.html.haml
app/views/admin/users/_form.html.haml
... | ... | @@ -61,7 +61,7 @@ |
61 | 61 | .span4 |
62 | 62 | - unless @admin_user.new_record? |
63 | 63 | .alert.alert-error |
64 | - - if @admin_user.blocked | |
64 | + - if @admin_user.blocked? | |
65 | 65 | %p This user is blocked and is not able to login to GitLab |
66 | 66 | = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn btn-small" |
67 | 67 | - else | ... | ... |
app/views/admin/users/index.html.haml
... | ... | @@ -53,7 +53,7 @@ |
53 | 53 | |
54 | 54 | = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small" |
55 | 55 | - unless user == current_user |
56 | - - if user.blocked | |
56 | + - if user.blocked? | |
57 | 57 | = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success" |
58 | 58 | - else |
59 | 59 | = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove" | ... | ... |
app/views/admin/users/show.html.haml
app/views/commits/_commits.html.haml
app/views/events/event/_note.html.haml
... | ... | @@ -21,9 +21,10 @@ |
21 | 21 | = event.project_name |
22 | 22 | |
23 | 23 | .event-body |
24 | - %i.icon-comment-alt.event-note-icon | |
25 | - %span.event-note | |
26 | - = markdown truncate(event.target.note, length: 70) | |
24 | + .event-note | |
25 | + .md | |
26 | + %i.icon-comment-alt.event-note-icon | |
27 | + = sanitize(markdown(truncate(event.target.note, length: 150)), tags: %w(a img b pre p)) | |
27 | 28 | - note = event.target |
28 | 29 | - if note.attachment.url |
29 | 30 | = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do | ... | ... |
app/views/groups/edit.html.haml
... | ... | @@ -9,8 +9,15 @@ |
9 | 9 | Group name is |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" |
12 | - | |
13 | - = f.submit 'Save group', class: "btn btn-save" | |
12 | + | |
13 | + .clearfix.group-description-holder | |
14 | + = f.label :description, "Details" | |
15 | + .input | |
16 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
17 | + | |
18 | + .form-actions | |
19 | + = f.submit 'Save group', class: "btn btn-save" | |
20 | + | |
14 | 21 | %hr |
15 | 22 | |
16 | 23 | ... | ... |
app/views/groups/new.html.haml
... | ... | @@ -9,9 +9,16 @@ |
9 | 9 | Group name is |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" |
12 | - | |
13 | - = f.submit 'Create group', class: "btn btn-create" | |
14 | - %hr | |
12 | + | |
13 | + .clearfix.group-description-holder | |
14 | + = f.label :description, "Details" | |
15 | + .input | |
16 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
17 | + | |
18 | + .form-actions | |
19 | + = f.submit 'Create group', class: "btn btn-create" | |
20 | + | |
21 | + | |
15 | 22 | .padded |
16 | 23 | %ul |
17 | 24 | %li Group is kind of directory for several projects | ... | ... |
app/views/groups/show.html.haml
... | ... | @@ -12,6 +12,9 @@ |
12 | 12 | %p.nothing_here_message Project activity will be displayed here |
13 | 13 | .loading.hide |
14 | 14 | .side.span4 |
15 | + - if @group.description.present? | |
16 | + .description.well.light | |
17 | + = @group.description | |
15 | 18 | = render "projects", projects: @projects |
16 | 19 | %div |
17 | 20 | %span.rss-icon | ... | ... |
app/views/issues/_filter.html.haml
... | ... | @@ -6,7 +6,10 @@ |
6 | 6 | Open |
7 | 7 | %li{class: ("active" if params[:status] == 'assigned-to-me')} |
8 | 8 | = link_to project_issues_path(@project, status: 'assigned-to-me') do |
9 | - Assigned To Me | |
9 | + Assigned to me | |
10 | + %li{class: ("active" if params[:status] == 'created-by-me')} | |
11 | + = link_to project_issues_path(@project, status: 'created-by-me') do | |
12 | + Created by me | |
10 | 13 | %li{class: ("active" if params[:status] == 'closed')} |
11 | 14 | = link_to project_issues_path(@project, status: 'closed') do |
12 | 15 | Closed | ... | ... |
app/views/layouts/_flash.html.haml
app/views/layouts/_head.html.haml
app/views/layouts/_head_panel.html.haml
... | ... | @@ -8,6 +8,9 @@ |
8 | 8 | %span.separator |
9 | 9 | %h1.project_name= title |
10 | 10 | %ul.nav |
11 | + %li | |
12 | + = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do | |
13 | + %i.icon-globe | |
11 | 14 | - if current_user.is_admin? |
12 | 15 | %li |
13 | 16 | = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do | ... | ... |
app/views/layouts/admin.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: "Admin area" |
4 | 4 | %body{class: "#{app_theme} admin"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: "Admin area" |
6 | + = render "layouts/flash" | |
7 | 7 | .container |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(controller: :dashboard, html_options: {class: 'home'}) do | ... | ... |
app/views/layouts/application.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: "Dashboard" |
4 | 4 | %body{class: "#{app_theme} application"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: "Dashboard" |
6 | + = render "layouts/flash" | |
7 | 7 | .container |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do | ... | ... |
app/views/layouts/devise.html.haml
app/views/layouts/errors.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: "Error" |
4 | 4 | %body{class: "#{app_theme} application"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: "" |
6 | + = render "layouts/flash" | |
7 | 7 | .container |
8 | 8 | .content |
9 | 9 | %center.padded.prepend-top-20 | ... | ... |
app/views/layouts/group.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: "#{@group.name}" |
4 | 4 | %body{class: "#{app_theme} application"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: "group: #{@group.name}" |
6 | + = render "layouts/flash" | |
7 | 7 | .container |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(path: 'groups#show', html_options: {class: 'home'}) do | ... | ... |
app/views/layouts/profile.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: "Profile" |
4 | 4 | %body{class: "#{app_theme} profile"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: "Profile" |
6 | + = render "layouts/flash" | |
7 | 7 | .container |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do | ... | ... |
app/views/layouts/project_resource.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: @project.name_with_namespace |
4 | 4 | %body{class: "#{app_theme} project"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: project_title(@project) |
6 | + = render "layouts/flash" | |
7 | 7 | - if can?(current_user, :download_code, @project) |
8 | 8 | = render 'shared/no_ssh' |
9 | 9 | |
... | ... | @@ -22,11 +22,12 @@ |
22 | 22 | = nav_link(controller: %w(graph)) do |
23 | 23 | = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref) |
24 | 24 | |
25 | - - if @project.issues_enabled | |
25 | + - if @project.issues_enabled | |
26 | 26 | = nav_link(controller: %w(issues milestones labels)) do |
27 | - = link_to project_issues_filter_path(@project) do | |
27 | + = link_to url_for_project_issues do | |
28 | 28 | Issues |
29 | - %span.count.issue_counter= @project.issues.opened.count | |
29 | + - if @project.used_default_issues_tracker? | |
30 | + %span.count.issue_counter= @project.issues.opened.count | |
30 | 31 | |
31 | 32 | - if @project.repo_exists? && @project.merge_requests_enabled |
32 | 33 | = nav_link(controller: :merge_requests) do | ... | ... |
app/views/layouts/user_team.html.haml
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | %html{ lang: "en"} |
3 | 3 | = render "layouts/head", title: "#{@team.name}" |
4 | 4 | %body{class: "#{app_theme} application"} |
5 | - = render "layouts/flash" | |
6 | 5 | = render "layouts/head_panel", title: "team: #{@team.name}" |
6 | + = render "layouts/flash" | |
7 | 7 | .container |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(path: 'teams#show', html_options: {class: 'home'}) do | ... | ... |
app/views/merge_requests/show/_mr_accept.html.haml
1 | -- unless can?(current_user, :accept_mr, @project) | |
1 | +- unless @allowed_to_merge | |
2 | 2 | .alert |
3 | - %strong Only masters can accept MR | |
3 | + %strong You don't have enough permissions to merge this MR | |
4 | 4 | |
5 | 5 | |
6 | -- if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project) | |
6 | +- if @show_merge_controls | |
7 | 7 | .automerge_widget.can_be_merged{style: "display:none"} |
8 | 8 | .alert.alert-success |
9 | 9 | %span | ... | ... |
... | ... | @@ -0,0 +1,9 @@ |
1 | +New Merge Request <%= @merge_request.id %> | |
2 | + | |
3 | +<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %> | |
4 | + | |
5 | + | |
6 | +Branches: <%= @merge_request.source_branch %> to <%= @merge_request.target_branch %> | |
7 | +Author: <%= @merge_request.author_name %> | |
8 | +Asignee: <%= @merge_request.assignee_name %> | |
9 | + | ... | ... |
... | ... | @@ -0,0 +1,10 @@ |
1 | +Hi <%= @user.name %>! | |
2 | + | |
3 | +Administrator created account for you. Now you are a member of company GitLab application. | |
4 | + | |
5 | +login.................. <%= @user.email %> | |
6 | +<% unless Gitlab.config.gitlab.signup_enabled %> | |
7 | + password............... <%= @password %> | |
8 | +<% end %> | |
9 | + | |
10 | +Click here to login: <%= url_for(root_url) %> | ... | ... |
app/views/notify/reassigned_merge_request_email.text.erb
0 → 100644
app/views/projects/_form.html.haml
... | ... | @@ -24,6 +24,15 @@ |
24 | 24 | = f.check_box :issues_enabled |
25 | 25 | %span.descr Lightweight issue tracking system for this project |
26 | 26 | |
27 | + - if Project.issues_tracker.values.count > 1 | |
28 | + .control-group | |
29 | + = f.label :issues_tracker, "Issues tracker", class: 'control-label' | |
30 | + .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) | |
31 | + | |
32 | + .clearfix | |
33 | + = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' | |
34 | + .input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id? | |
35 | + | |
27 | 36 | .control-group |
28 | 37 | = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' |
29 | 38 | .controls | ... | ... |
app/views/projects/empty.html.haml
... | ... | @@ -5,14 +5,14 @@ |
5 | 5 | %fieldset |
6 | 6 | %legend Git global setup: |
7 | 7 | %pre.dark |
8 | - = preserve do | |
8 | + :preserve | |
9 | 9 | git config --global user.name "#{current_user.name}" |
10 | 10 | git config --global user.email "#{current_user.email}" |
11 | 11 | |
12 | 12 | %fieldset |
13 | 13 | %legend Create Repository |
14 | 14 | %pre.dark |
15 | - = preserve do | |
15 | + :preserve | |
16 | 16 | mkdir #{@project.path} |
17 | 17 | cd #{@project.path} |
18 | 18 | git init |
... | ... | @@ -25,7 +25,7 @@ |
25 | 25 | %fieldset |
26 | 26 | %legend Existing Git Repo? |
27 | 27 | %pre.dark |
28 | - = preserve do | |
28 | + :preserve | |
29 | 29 | cd existing_git_repo |
30 | 30 | git remote add origin #{@project.url_to_repo} |
31 | 31 | git push -u origin master | ... | ... |
app/views/public/projects/index.html.haml
app/views/shared/_clone_panel.html.haml
1 | 1 | .input-prepend.project_clone_holder |
2 | 2 | %button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH |
3 | 3 | %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase |
4 | - | |
5 | - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge" | |
4 | + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge", readonly: true | ... | ... |
app/views/team_members/_team_member.html.haml
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | %span.label This is you! |
21 | 21 | - if @project.namespace_owner == user |
22 | 22 | %span.label Owner |
23 | - - elsif user.blocked | |
23 | + - elsif user.blocked? | |
24 | 24 | %span.label Blocked |
25 | 25 | - elsif allow_admin |
26 | 26 | = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do | ... | ... |
app/views/teams/edit.html.haml
... | ... | @@ -12,13 +12,19 @@ |
12 | 12 | .input |
13 | 13 | = f.text_field :name, placeholder: "Ex. OpenSource", class: "xlarge left" |
14 | 14 | |
15 | + .clearfix.team-description-holder | |
16 | + = f.label :description, "Details" | |
17 | + .input | |
18 | + = f.text_area :description, maxlength: 250, class: "xlarge js-gfm-input", rows: 4 | |
19 | + | |
15 | 20 | .clearfix |
16 | 21 | = f.label :path do |
17 | 22 | Team path is |
18 | 23 | .input |
19 | 24 | = f.text_field :path, placeholder: "opensource", class: "xlarge left" |
25 | + | |
20 | 26 | .form-actions |
21 | - = f.submit 'Save team changes', class: "btn btn-save" | |
27 | + = f.submit 'Save team changes', class: "btn btn-primary" | |
22 | 28 | .span5 |
23 | 29 | .ui-box |
24 | 30 | %h5.title Remove team |
... | ... | @@ -26,4 +32,3 @@ |
26 | 32 | %p |
27 | 33 | Removed team can not be restored! |
28 | 34 | = link_to 'Remove team', team_path(@team), method: :delete, confirm: "You are sure?", class: "btn btn-remove btn-small" |
29 | - | ... | ... |
app/views/teams/members/_show.html.haml
... | ... | @@ -23,7 +23,7 @@ |
23 | 23 | %span.btn.disabled This is you! |
24 | 24 | - if @team.owner == user |
25 | 25 | %span.btn.disabled Owner |
26 | - - elsif user.blocked | |
26 | + - elsif user.blocked? | |
27 | 27 | %span.btn.disabled.blocked Blocked |
28 | 28 | - elsif allow_admin |
29 | 29 | = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove", title: "Remove from team" do | ... | ... |
app/views/teams/new.html.haml
... | ... | @@ -9,9 +9,15 @@ |
9 | 9 | Team name is |
10 | 10 | .input |
11 | 11 | = f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left" |
12 | - | |
13 | - = f.submit 'Create team', class: "btn btn-create" | |
14 | - %hr | |
12 | + | |
13 | + .clearfix.team-description-holder | |
14 | + = f.label :description, "Details" | |
15 | + .input | |
16 | + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 | |
17 | + | |
18 | + .form-actions | |
19 | + = f.submit 'Create team', class: "btn btn-create" | |
20 | + | |
15 | 21 | .padded |
16 | 22 | %ul |
17 | 23 | %li All created teams are public (users can view who enter into team and which project are assigned for this team) | ... | ... |
app/views/teams/show.html.haml
... | ... | @@ -11,6 +11,9 @@ |
11 | 11 | %p.nothing_here_message Projects activity will be displayed here |
12 | 12 | .loading.hide |
13 | 13 | .side.span4 |
14 | + - if @team.description.present? | |
15 | + .description.well.light | |
16 | + = @team.description | |
14 | 17 | = render "projects", projects: @projects |
15 | 18 | %div |
16 | 19 | %span.rss-icon | ... | ... |
config/gitlab.yml.example
1 | 1 | # # # # # # # # # # # # # # # # # # |
2 | -# Gitlab application config file # | |
2 | +# GitLab application config file # | |
3 | 3 | # # # # # # # # # # # # # # # # # # |
4 | 4 | # |
5 | 5 | # How to use: |
... | ... | @@ -37,9 +37,25 @@ production: &base |
37 | 37 | # signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled. |
38 | 38 | # username_changing_enabled: false # default: true - User can change her username/namespace |
39 | 39 | |
40 | + | |
41 | + ## External issues trackers | |
42 | + issues_tracker: | |
43 | + redmine: | |
44 | + ## If not nil, link 'Issues' on project page will be replaced with this | |
45 | + ## Use placeholders: | |
46 | + ## :project_id - GitLab project identifier | |
47 | + ## :issues_tracker_id - Project Name or Id in external issue tracker | |
48 | + project_url: "http://redmine.sample/projects/:issues_tracker_id" | |
49 | + ## If not nil, links from /#\d/ entities from commit messages will replaced with this | |
50 | + ## Use placeholders: | |
51 | + ## :project_id - GitLab project identifier | |
52 | + ## :issues_tracker_id - Project Name or Id in external issue tracker | |
53 | + ## :id - Issue id (from commit messages) | |
54 | + issues_url: "http://redmine.sample/issues/:id" | |
55 | + | |
40 | 56 | ## Gravatar |
41 | 57 | gravatar: |
42 | - enabled: true # Use user avatar images from Gravatar.com (default: true) | |
58 | + enabled: true # Use user avatar image from Gravatar.com (default: true) | |
43 | 59 | # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm |
44 | 60 | # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm |
45 | 61 | |
... | ... | @@ -60,22 +76,21 @@ production: &base |
60 | 76 | bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' |
61 | 77 | password: '_the_password_of_the_bind_user' |
62 | 78 | |
63 | - ## Omniauth settings | |
79 | + ## OmniAuth settings | |
64 | 80 | omniauth: |
65 | - # Enable ability for users | |
66 | - # Allow logging in via Twitter, Google, etc. using Omniauth providers | |
81 | + # Allow login via Twitter, Google, etc. using OmniAuth providers | |
67 | 82 | enabled: false |
68 | 83 | |
69 | 84 | # CAUTION! |
70 | - # This allows users to login without having a user account first (default: false) | |
85 | + # This allows users to login without having a user account first (default: false). | |
71 | 86 | # User accounts will be created automatically when authentication was successful. |
72 | 87 | allow_single_sign_on: false |
73 | - # Locks down those users until they have been cleared by the admin (default: true) | |
88 | + # Locks down those users until they have been cleared by the admin (default: true). | |
74 | 89 | block_auto_created_users: true |
75 | 90 | |
76 | 91 | ## Auth providers |
77 | - # Uncomment the lines and fill in the data of the auth provider you want to use | |
78 | - # If your favorite auth provider is not listed you can user others: | |
92 | + # Uncomment the following lines and fill in the data of the auth provider you want to use | |
93 | + # If your favorite auth provider is not listed you can use others: | |
79 | 94 | # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers |
80 | 95 | # The 'app_id' and 'app_secret' parameters are always passed as the first two |
81 | 96 | # arguments, followed by optional 'args' which can be either a hash or an array. |
... | ... | @@ -114,7 +129,7 @@ production: &base |
114 | 129 | upload_pack: true |
115 | 130 | receive_pack: true |
116 | 131 | |
117 | - # If you use non-standart ssh port you need to specify it | |
132 | + # If you use non-standard ssh port you need to specify it | |
118 | 133 | # ssh_port: 22 |
119 | 134 | |
120 | 135 | ## Git settings |
... | ... | @@ -122,10 +137,10 @@ production: &base |
122 | 137 | # Use the default values unless you really know what you are doing |
123 | 138 | git: |
124 | 139 | bin_path: /usr/bin/git |
125 | - # Max size of git object like commit, in bytes | |
126 | - # This value can be increased if you have a very large commits | |
140 | + # Max size of a git object (e.g. a commit), in bytes | |
141 | + # This value can be increased if you have very large commits | |
127 | 142 | max_size: 5242880 # 5.megabytes |
128 | - # Git timeout to read commit, in seconds | |
143 | + # Git timeout to read a commit, in seconds | |
129 | 144 | timeout: 10 |
130 | 145 | |
131 | 146 | development: |
... | ... | @@ -133,6 +148,10 @@ development: |
133 | 148 | |
134 | 149 | test: |
135 | 150 | <<: *base |
151 | + issues_tracker: | |
152 | + redmine: | |
153 | + project_url: "http://redmine/projects/:issues_tracker_id" | |
154 | + issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" | |
136 | 155 | |
137 | 156 | staging: |
138 | 157 | <<: *base | ... | ... |
config/initializers/1_settings.rb
... | ... | @@ -42,6 +42,8 @@ Settings['omniauth'] ||= Settingslogic.new({}) |
42 | 42 | Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? |
43 | 43 | Settings.omniauth['providers'] ||= [] |
44 | 44 | |
45 | +Settings['issues_tracker'] ||= {} | |
46 | + | |
45 | 47 | # |
46 | 48 | # GitLab |
47 | 49 | # |
... | ... | @@ -50,7 +52,7 @@ Settings.gitlab['default_projects_limit'] ||= 10 |
50 | 52 | Settings.gitlab['host'] ||= 'localhost' |
51 | 53 | Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? |
52 | 54 | Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 |
53 | -Settings.gitlab['relative_url_root'] ||= '' | |
55 | +Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' | |
54 | 56 | Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" |
55 | 57 | Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" |
56 | 58 | Settings.gitlab['support_email'] ||= Settings.gitlab.email_from | ... | ... |
config/initializers/devise.rb
... | ... | @@ -99,7 +99,7 @@ Devise.setup do |config| |
99 | 99 | |
100 | 100 | # ==> Configuration for :validatable |
101 | 101 | # Range for password length. Default is 6..128. |
102 | - # config.password_length = 6..128 | |
102 | + config.password_length = 6..128 | |
103 | 103 | |
104 | 104 | # Email regex used to validate email formats. It simply asserts that |
105 | 105 | # an one (and only one) @ exists in the given string. This is mainly | ... | ... |
config/locales/devise.en.yml
... | ... | @@ -17,6 +17,7 @@ en: |
17 | 17 | unauthenticated: 'You need to sign in before continuing.' |
18 | 18 | unconfirmed: 'You have to confirm your account before continuing.' |
19 | 19 | locked: 'Your account is locked.' |
20 | + not_found_in_database: 'Invalid email or password.' | |
20 | 21 | invalid: 'Invalid email or password.' |
21 | 22 | invalid_token: 'Invalid authentication token.' |
22 | 23 | timeout: 'Your session expired, please sign in again to continue.' | ... | ... |
config/routes.rb
... | ... | @@ -49,7 +49,7 @@ Gitlab::Application.routes.draw do |
49 | 49 | # |
50 | 50 | # Attachments serving |
51 | 51 | # |
52 | - get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /[a-zA-Z.0-9_\-\+]+/ } | |
52 | + get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } | |
53 | 53 | |
54 | 54 | # |
55 | 55 | # Admin Area | ... | ... |
db/migrate/20130123114545_add_issues_tracker_to_project.rb
0 → 100644
db/migrate/20130206084024_add_description_to_namsespace.rb
0 → 100644
db/migrate/20130211085435_add_issues_tracker_id_to_project.rb
0 → 100644
db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
1 | 1 | class ConvertClosedToStateInIssue < ActiveRecord::Migration |
2 | 2 | def up |
3 | 3 | Issue.transaction do |
4 | - Issue.where(closed: true).update_all("state = 'closed'") | |
5 | - Issue.where(closed: false).update_all("state = 'opened'") | |
4 | + Issue.where(closed: true).update_all(state: :closed) | |
5 | + Issue.where(closed: false).update_all(state: :opened) | |
6 | 6 | end |
7 | 7 | end |
8 | 8 | |
9 | 9 | def down |
10 | 10 | Issue.transaction do |
11 | - Issue.where(state: :closed).update_all("closed = 1") | |
11 | + Issue.where(state: :closed).update_all(closed: true) | |
12 | 12 | end |
13 | 13 | end |
14 | 14 | end | ... | ... |
db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
1 | 1 | class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration |
2 | 2 | def up |
3 | 3 | MergeRequest.transaction do |
4 | - MergeRequest.where(closed: true, merged: true).update_all("state = 'merged'") | |
5 | - MergeRequest.where(closed: true, merged: true).update_all("state = 'closed'") | |
6 | - MergeRequest.where(closed: false).update_all("state = 'opened'") | |
4 | + MergeRequest.where(closed: true, merged: true).update_all(state: :merged) | |
5 | + MergeRequest.where(closed: true, merged: false).update_all(state: :closed) | |
6 | + MergeRequest.where(closed: false).update_all(state: :opened) | |
7 | 7 | end |
8 | 8 | end |
9 | 9 | ... | ... |
db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
1 | 1 | class ConvertClosedToStateInMilestone < ActiveRecord::Migration |
2 | 2 | def up |
3 | 3 | Milestone.transaction do |
4 | - Milestone.where(closed: false).update_all("state = 'opened'") | |
5 | - Milestone.where(closed: false).update_all("state = 'active'") | |
4 | + Milestone.where(closed: true).update_all(state: :closed) | |
5 | + Milestone.where(closed: false).update_all(state: :active) | |
6 | 6 | end |
7 | 7 | end |
8 | 8 | |
9 | 9 | def down |
10 | 10 | Milestone.transaction do |
11 | - Milestone.where(state: :closed).update_all("closed = 1") | |
11 | + Milestone.where(state: :closed).update_all(closed: true) | |
12 | 12 | end |
13 | 13 | end |
14 | 14 | end | ... | ... |
... | ... | @@ -0,0 +1,14 @@ |
1 | +class ConvertBlockedToState < ActiveRecord::Migration | |
2 | + def up | |
3 | + User.transaction do | |
4 | + User.where(blocked: true).update_all(state: :blocked) | |
5 | + User.where(blocked: false).update_all(state: :active) | |
6 | + end | |
7 | + end | |
8 | + | |
9 | + def down | |
10 | + User.transaction do | |
11 | + User.where(state: :blocked).update_all(blocked: :true) | |
12 | + end | |
13 | + end | |
14 | +end | ... | ... |
db/schema.rb
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | # |
12 | 12 | # It's strongly recommended to check this file into your version control system. |
13 | 13 | |
14 | -ActiveRecord::Schema.define(:version => 20130220133245) do | |
14 | +ActiveRecord::Schema.define(:version => 20130304105317) do | |
15 | 15 | |
16 | 16 | create_table "events", :force => true do |t| |
17 | 17 | t.string "target_type" |
... | ... | @@ -112,6 +112,7 @@ ActiveRecord::Schema.define(:version => 20130220133245) do |
112 | 112 | t.datetime "created_at", :null => false |
113 | 113 | t.datetime "updated_at", :null => false |
114 | 114 | t.string "type" |
115 | + t.string "description", :default => "", :null => false | |
115 | 116 | end |
116 | 117 | |
117 | 118 | add_index "namespaces", ["name"], :name => "index_namespaces_on_name" |
... | ... | @@ -152,6 +153,8 @@ ActiveRecord::Schema.define(:version => 20130220133245) do |
152 | 153 | t.boolean "wiki_enabled", :default => true, :null => false |
153 | 154 | t.integer "namespace_id" |
154 | 155 | t.boolean "public", :default => false, :null => false |
156 | + t.string "issues_tracker", :default => "gitlab", :null => false | |
157 | + t.string "issues_tracker_id" | |
155 | 158 | end |
156 | 159 | |
157 | 160 | add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" |
... | ... | @@ -232,6 +235,7 @@ ActiveRecord::Schema.define(:version => 20130220133245) do |
232 | 235 | t.integer "owner_id" |
233 | 236 | t.datetime "created_at", :null => false |
234 | 237 | t.datetime "updated_at", :null => false |
238 | + t.string "description", :default => "", :null => false | |
235 | 239 | end |
236 | 240 | |
237 | 241 | create_table "users", :force => true do |t| |
... | ... | @@ -257,7 +261,6 @@ ActiveRecord::Schema.define(:version => 20130220133245) do |
257 | 261 | t.boolean "dark_scheme", :default => false, :null => false |
258 | 262 | t.integer "theme_id", :default => 1, :null => false |
259 | 263 | t.string "bio" |
260 | - t.boolean "blocked", :default => false, :null => false | |
261 | 264 | t.integer "failed_attempts", :default => 0 |
262 | 265 | t.datetime "locked_at" |
263 | 266 | t.string "extern_uid" |
... | ... | @@ -265,10 +268,10 @@ ActiveRecord::Schema.define(:version => 20130220133245) do |
265 | 268 | t.string "username" |
266 | 269 | t.boolean "can_create_group", :default => true, :null => false |
267 | 270 | t.boolean "can_create_team", :default => true, :null => false |
271 | + t.string "state" | |
268 | 272 | end |
269 | 273 | |
270 | 274 | add_index "users", ["admin"], :name => "index_users_on_admin" |
271 | - add_index "users", ["blocked"], :name => "index_users_on_blocked" | |
272 | 275 | add_index "users", ["email"], :name => "index_users_on_email", :unique => true |
273 | 276 | add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true |
274 | 277 | add_index "users", ["name"], :name => "index_users_on_name" | ... | ... |
doc/install/databases.md
... | ... | @@ -12,7 +12,7 @@ GitLab supports the following databases: |
12 | 12 | sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev |
13 | 13 | |
14 | 14 | # Login to MySQL |
15 | - $ mysql -u root -p | |
15 | + mysql -u root -p | |
16 | 16 | |
17 | 17 | # Create a user for GitLab. (change $password to a real password) |
18 | 18 | mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password'; | ... | ... |
doc/install/installation.md
1 | -This installation guide was created for Debian/Ubuntu and tested on it. | |
2 | - | |
3 | -Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements. | |
1 | +This installation guide was created for Debian/Ubuntu and tested on it. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements. | |
4 | 2 | |
3 | +This installation guide is recommended to set up a production server. If you want a development environment please use the [Vargrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) since it makes it much easier to set up all the dependencies for integration testing. | |
5 | 4 | |
6 | 5 | **Important Note:** |
7 | 6 | The following steps have been known to work. |
... | ... | @@ -92,21 +91,29 @@ Create a `git` user for Gitlab: |
92 | 91 | |
93 | 92 | sudo adduser --disabled-login --gecos 'GitLab' git |
94 | 93 | |
94 | + | |
95 | 95 | # 4. GitLab shell |
96 | 96 | |
97 | - # Login as git | |
97 | +GitLab Shell is a ssh access and repository management software developed specially for GitLab. | |
98 | + | |
99 | + # Login as git | |
98 | 100 | sudo su git |
99 | 101 | |
100 | - # Go to home directory | |
102 | + # Go to home directory | |
101 | 103 | cd /home/git |
102 | 104 | |
103 | 105 | # Clone gitlab shell |
104 | 106 | git clone https://github.com/gitlabhq/gitlab-shell.git |
105 | 107 | |
106 | - # Setup | |
107 | 108 | cd gitlab-shell |
108 | 109 | cp config.yml.example config.yml |
109 | - ./bin/install | |
110 | + | |
111 | + # Edit config and replace gitlab_url | |
112 | + # with something like 'http://domain.com/' | |
113 | + vim config.yml | |
114 | + | |
115 | + # Do setup | |
116 | + ./bin/install | |
110 | 117 | |
111 | 118 | |
112 | 119 | # 5. Database |
... | ... | @@ -124,9 +131,9 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install |
124 | 131 | # Clone GitLab repository |
125 | 132 | sudo -u git -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab |
126 | 133 | |
127 | - # Go to gitlab dir | |
134 | + # Go to gitlab dir | |
128 | 135 | cd /home/git/gitlab |
129 | - | |
136 | + | |
130 | 137 | # Checkout to stable release |
131 | 138 | sudo -u git -H git checkout 5-0-stable |
132 | 139 | |
... | ... | @@ -157,7 +164,7 @@ do so with caution! |
157 | 164 | # Create directory for pids and make sure GitLab can write to it |
158 | 165 | sudo -u git -H mkdir tmp/pids/ |
159 | 166 | sudo chmod -R u+rwX tmp/pids/ |
160 | - | |
167 | + | |
161 | 168 | # Copy the example Unicorn config |
162 | 169 | sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb |
163 | 170 | |
... | ... | @@ -188,7 +195,7 @@ Make sure to update username/password in config/database.yml. |
188 | 195 | |
189 | 196 | |
190 | 197 | ## Initialise Database and Activate Advanced Features |
191 | - | |
198 | + | |
192 | 199 | sudo -u git -H bundle exec rake db:setup RAILS_ENV=production |
193 | 200 | sudo -u git -H bundle exec rake db:seed_fu RAILS_ENV=production |
194 | 201 | sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production |
... | ... | @@ -286,7 +293,7 @@ a different host, you can configure its connection string via the |
286 | 293 | ## Custom SSH Connection |
287 | 294 | |
288 | 295 | If you are running SSH on a non-standard port, you must change the gitlab user's SSH config. |
289 | - | |
296 | + | |
290 | 297 | # Add to /home/git/.ssh/config |
291 | 298 | host localhost # Give your setup a name (here: override localhost) |
292 | 299 | user git # Your remote git user | ... | ... |
features/project/network.feature
... | ... | @@ -7,3 +7,19 @@ Feature: Project Network Graph |
7 | 7 | @javascript |
8 | 8 | Scenario: I should see project network |
9 | 9 | Then page should have network graph |
10 | + And page should select "master" in select box | |
11 | + And page should have "master" on graph | |
12 | + | |
13 | + @javascript | |
14 | + Scenario: I should switch ref to "stable" | |
15 | + When I switch ref to "stable" | |
16 | + Then page should have network graph | |
17 | + And page should select "stable" in select box | |
18 | + And page should have "stable" on graph | |
19 | + | |
20 | + @javascript | |
21 | + Scenario: I should looking for a commit by SHA of "v2.1.0" | |
22 | + When I looking for a commit by SHA of "v2.1.0" | |
23 | + Then page should have network graph | |
24 | + And page should select "master" in select box | |
25 | + And page should have "v2.1.0" on graph | ... | ... |
features/steps/admin/admin_groups.rb
... | ... | @@ -25,11 +25,13 @@ class AdminGroups < Spinach::FeatureSteps |
25 | 25 | |
26 | 26 | And 'submit form with new group info' do |
27 | 27 | fill_in 'group_name', :with => 'gitlab' |
28 | + fill_in 'group_description', :with => 'Group description' | |
28 | 29 | click_button "Create group" |
29 | 30 | end |
30 | 31 | |
31 | 32 | Then 'I should see newly created group' do |
32 | 33 | page.should have_content "Group: gitlab" |
34 | + page.should have_content "Group description" | |
33 | 35 | end |
34 | 36 | |
35 | 37 | Then 'I should be redirected to group page' do | ... | ... |
features/steps/admin/admin_teams.rb
... | ... | @@ -18,6 +18,7 @@ class AdminTeams < Spinach::FeatureSteps |
18 | 18 | |
19 | 19 | And 'submit form with new team info' do |
20 | 20 | fill_in 'user_team_name', with: 'gitlab' |
21 | + fill_in 'user_team_description', with: 'description' | |
21 | 22 | click_button 'Create team' |
22 | 23 | end |
23 | 24 | |
... | ... | @@ -27,6 +28,7 @@ class AdminTeams < Spinach::FeatureSteps |
27 | 28 | |
28 | 29 | And 'I should see newly created team' do |
29 | 30 | page.should have_content "Team: gitlab" |
31 | + page.should have_content "description" | |
30 | 32 | end |
31 | 33 | |
32 | 34 | When 'I visit admin teams page' do | ... | ... |
features/steps/group/group.rb
... | ... | @@ -28,7 +28,7 @@ class Groups < Spinach::FeatureSteps |
28 | 28 | |
29 | 29 | Then 'I should see merge requests from this group assigned to me' do |
30 | 30 | assigned_to_me(:merge_requests).each do |issue| |
31 | - page.should have_content issue.title | |
31 | + page.should have_content issue.title[0..80] | |
32 | 32 | end |
33 | 33 | end |
34 | 34 | |
... | ... | @@ -69,12 +69,14 @@ class Groups < Spinach::FeatureSteps |
69 | 69 | end |
70 | 70 | |
71 | 71 | And 'submit form with new group info' do |
72 | - fill_in 'group_name', :with => 'Samurai' | |
72 | + fill_in 'group_name', with: 'Samurai' | |
73 | + fill_in 'group_description', with: 'Tokugawa Shogunate' | |
73 | 74 | click_button "Create group" |
74 | 75 | end |
75 | 76 | |
76 | 77 | Then 'I should see newly created group' do |
77 | 78 | page.should have_content "Samurai" |
79 | + page.should have_content "Tokugawa Shogunate" | |
78 | 80 | page.should have_content "You will only see events from projects in this group" |
79 | 81 | end |
80 | 82 | ... | ... |
features/steps/project/project_merge_requests.rb
... | ... | @@ -25,8 +25,8 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
25 | 25 | end |
26 | 26 | |
27 | 27 | Then 'I should see closed merge request "Bug NS-04"' do |
28 | - mr = MergeRequest.find_by_title("Bug NS-04") | |
29 | - mr.closed?.should be_true | |
28 | + merge_request = MergeRequest.find_by_title!("Bug NS-04") | |
29 | + merge_request.closed?.should be_true | |
30 | 30 | page.should have_content "Closed by" |
31 | 31 | end |
32 | 32 | |
... | ... | @@ -63,7 +63,6 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
63 | 63 | end |
64 | 64 | |
65 | 65 | And 'project "Shop" have "Bug NS-04" open merge request' do |
66 | - project = Project.find_by_name("Shop") | |
67 | 66 | create(:merge_request, |
68 | 67 | title: "Bug NS-04", |
69 | 68 | project: project, |
... | ... | @@ -71,7 +70,6 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
71 | 70 | end |
72 | 71 | |
73 | 72 | And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do |
74 | - project = Project.find_by_name("Shop") | |
75 | 73 | create(:merge_request_with_diffs, |
76 | 74 | title: "Bug NS-05", |
77 | 75 | project: project, |
... | ... | @@ -79,7 +77,6 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
79 | 77 | end |
80 | 78 | |
81 | 79 | And 'project "Shop" have "Feature NS-03" closed merge request' do |
82 | - project = Project.find_by_name("Shop") | |
83 | 80 | create(:closed_merge_request, |
84 | 81 | title: "Feature NS-03", |
85 | 82 | project: project, |
... | ... | @@ -87,18 +84,16 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
87 | 84 | end |
88 | 85 | |
89 | 86 | And 'I switch to the diff tab' do |
90 | - mr = MergeRequest.find_by_title("Bug NS-05") | |
91 | - visit diffs_project_merge_request_path(mr.project, mr) | |
87 | + visit diffs_project_merge_request_path(project, merge_request) | |
92 | 88 | end |
93 | 89 | |
94 | 90 | And 'I switch to the merge request\'s comments tab' do |
95 | - mr = MergeRequest.find_by_title("Bug NS-05") | |
96 | - visit project_merge_request_path(mr.project, mr) | |
91 | + visit project_merge_request_path(project, merge_request) | |
97 | 92 | end |
98 | 93 | |
99 | 94 | And 'I click on the first commit in the merge request' do |
100 | - mr = MergeRequest.find_by_title("Bug NS-05") | |
101 | - click_link mr.commits.first.short_id(8) | |
95 | + | |
96 | + click_link merge_request.commits.first.short_id(8) | |
102 | 97 | end |
103 | 98 | |
104 | 99 | And 'I leave a comment on the diff page' do |
... | ... | @@ -121,8 +116,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
121 | 116 | end |
122 | 117 | |
123 | 118 | Then 'I should see a discussion has started on line 185' do |
124 | - mr = MergeRequest.find_by_title("Bug NS-05") | |
125 | - first_commit = mr.commits.first | |
119 | + first_commit = merge_request.commits.first | |
126 | 120 | first_diff = first_commit.diffs.first |
127 | 121 | page.should have_content "#{current_user.name} started a discussion on this merge request diff" |
128 | 122 | page.should have_content "#{first_diff.b_path}:L185" |
... | ... | @@ -130,8 +124,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
130 | 124 | end |
131 | 125 | |
132 | 126 | Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do |
133 | - mr = MergeRequest.find_by_title("Bug NS-05") | |
134 | - first_commit = mr.commits.first | |
127 | + first_commit = merge_request.commits.first | |
135 | 128 | first_diff = first_commit.diffs.first |
136 | 129 | page.should have_content "#{current_user.name} started a discussion on commit" |
137 | 130 | page.should have_content first_commit.short_id(8) |
... | ... | @@ -140,12 +133,19 @@ class ProjectMergeRequests < Spinach::FeatureSteps |
140 | 133 | end |
141 | 134 | |
142 | 135 | Then 'I should see a discussion has started on commit bcf03b5de6c' do |
143 | - mr = MergeRequest.find_by_title("Bug NS-05") | |
144 | - first_commit = mr.st_commits.first | |
136 | + first_commit = merge_request.st_commits.first | |
145 | 137 | first_diff = first_commit.diffs.first |
146 | 138 | page.should have_content "#{current_user.name} started a discussion on commit bcf03b5de6c" |
147 | 139 | page.should have_content first_commit.short_id(8) |
148 | 140 | page.should have_content "One comment to rule them all" |
149 | 141 | page.should have_content "#{first_diff.b_path}:L185" |
150 | 142 | end |
143 | + | |
144 | + def project | |
145 | + @project ||= Project.find_by_name!("Shop") | |
146 | + end | |
147 | + | |
148 | + def merge_request | |
149 | + @merge_request ||= MergeRequest.find_by_title!("Bug NS-05") | |
150 | + end | |
151 | 151 | end | ... | ... |
features/steps/project/project_network_graph.rb
... | ... | @@ -4,16 +4,51 @@ class ProjectNetworkGraph < Spinach::FeatureSteps |
4 | 4 | |
5 | 5 | Then 'page should have network graph' do |
6 | 6 | page.should have_content "Project Network Graph" |
7 | - within ".graph" do | |
8 | - page.should have_content "master" | |
9 | - end | |
7 | + page.should have_selector ".graph" | |
10 | 8 | end |
11 | 9 | |
12 | - And 'I visit project "Shop" network page' do | |
10 | + When 'I visit project "Shop" network page' do | |
13 | 11 | # Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650) |
14 | - Gitlab::Graph::JsonBuilder.stub(max_count: 10) | |
12 | + Graph::JsonBuilder.stub(max_count: 10) | |
15 | 13 | |
16 | 14 | project = Project.find_by_name("Shop") |
17 | 15 | visit project_graph_path(project, "master") |
18 | 16 | end |
17 | + | |
18 | + And 'page should select "master" in select box' do | |
19 | + page.should have_selector '#ref_chzn span', :text => "master" | |
20 | + end | |
21 | + | |
22 | + And 'page should have "master" on graph' do | |
23 | + within '.graph' do | |
24 | + page.should have_content 'master' | |
25 | + end | |
26 | + end | |
27 | + | |
28 | + And 'I switch ref to "stable"' do | |
29 | + page.select 'stable', :from => 'ref' | |
30 | + end | |
31 | + | |
32 | + And 'page should select "stable" in select box' do | |
33 | + page.should have_selector '#ref_chzn span', :text => "stable" | |
34 | + end | |
35 | + | |
36 | + And 'page should have "stable" on graph' do | |
37 | + within '.graph' do | |
38 | + page.should have_content 'stable' | |
39 | + end | |
40 | + end | |
41 | + | |
42 | + And 'I looking for a commit by SHA of "v2.1.0"' do | |
43 | + within ".content .search" do | |
44 | + fill_in 'q', :with => '98d6492' | |
45 | + find('button').click | |
46 | + end | |
47 | + end | |
48 | + | |
49 | + And 'page should have "v2.1.0" on graph' do | |
50 | + within '.graph' do | |
51 | + page.should have_content 'v2.1.0' | |
52 | + end | |
53 | + end | |
19 | 54 | end | ... | ... |
features/steps/shared/paths.rb
... | ... | @@ -143,7 +143,7 @@ module SharedPaths |
143 | 143 | |
144 | 144 | Given "I visit my project's network page" do |
145 | 145 | # Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650) |
146 | - Gitlab::Graph::JsonBuilder.stub(max_count: 10) | |
146 | + Graph::JsonBuilder.stub(max_count: 10) | |
147 | 147 | |
148 | 148 | visit project_graph_path(@project, root_ref) |
149 | 149 | end | ... | ... |
features/steps/userteams/userteams.rb
... | ... | @@ -44,9 +44,16 @@ class Userteams < Spinach::FeatureSteps |
44 | 44 | |
45 | 45 | And 'I submit form with new team info' do |
46 | 46 | fill_in 'name', with: 'gitlab' |
47 | + | |
48 | + fill_in 'user_team_description', with: 'team description' | |
47 | 49 | click_button 'Create team' |
48 | 50 | end |
49 | 51 | |
52 | + And 'I should see newly created team' do | |
53 | + page.should have_content "gitlab" | |
54 | + page.should have_content "team description" | |
55 | + end | |
56 | + | |
50 | 57 | Then 'I should be redirected to new team page' do |
51 | 58 | team = UserTeam.last |
52 | 59 | current_path.should == team_path(team) | ... | ... |
features/support/env.rb
... | ... | @@ -34,6 +34,7 @@ Spinach.hooks.before_scenario do |
34 | 34 | Gitlab.config.gitlab_shell.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path')) |
35 | 35 | FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path |
36 | 36 | FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path |
37 | + DatabaseCleaner.start | |
37 | 38 | end |
38 | 39 | |
39 | 40 | Spinach.hooks.after_scenario do | ... | ... |
features/teams/team.feature
... | ... | @@ -20,6 +20,7 @@ Feature: UserTeams |
20 | 20 | When I click to "New team" link |
21 | 21 | And I submit form with new team info |
22 | 22 | Then I should be redirected to new team page |
23 | + Then I should see newly created team | |
23 | 24 | |
24 | 25 | Scenario: I should see team dashboard list |
25 | 26 | When I have teams with projects and members | ... | ... |
lib/api/entities.rb
... | ... | @@ -2,11 +2,11 @@ module Gitlab |
2 | 2 | module Entities |
3 | 3 | class User < Grape::Entity |
4 | 4 | expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, |
5 | - :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider | |
5 | + :dark_scheme, :theme_id, :state, :created_at, :extern_uid, :provider | |
6 | 6 | end |
7 | 7 | |
8 | 8 | class UserBasic < Grape::Entity |
9 | - expose :id, :username, :email, :name, :blocked, :created_at | |
9 | + expose :id, :username, :email, :name, :state, :created_at | |
10 | 10 | end |
11 | 11 | |
12 | 12 | class UserLogin < UserBasic | ... | ... |
lib/api/projects.rb
... | ... | @@ -52,8 +52,8 @@ module Gitlab |
52 | 52 | :issues_enabled, |
53 | 53 | :wall_enabled, |
54 | 54 | :merge_requests_enabled, |
55 | - :wiki_enabled] | |
56 | - | |
55 | + :wiki_enabled, | |
56 | + :namespace_id] | |
57 | 57 | @project = ::Projects::CreateContext.new(current_user, attrs).execute |
58 | 58 | if @project.saved? |
59 | 59 | present @project, with: Entities::Project | ... | ... |
lib/extracts_path.rb
... | ... | @@ -126,7 +126,7 @@ module ExtractsPath |
126 | 126 | @tree = TreeDecorator.new(@tree) |
127 | 127 | |
128 | 128 | raise InvalidPathError if @tree.invalid? |
129 | - rescue NoMethodError, InvalidPathError | |
129 | + rescue RuntimeError, NoMethodError, InvalidPathError | |
130 | 130 | not_found! |
131 | 131 | end |
132 | 132 | end | ... | ... |
lib/gitlab/auth.rb
... | ... | @@ -41,10 +41,12 @@ module Gitlab |
41 | 41 | password_confirmation: password, |
42 | 42 | projects_limit: Gitlab.config.gitlab.default_projects_limit, |
43 | 43 | }, as: :admin) |
44 | + @user.save! | |
45 | + | |
44 | 46 | if Gitlab.config.omniauth['block_auto_created_users'] && !ldap |
45 | - @user.blocked = true | |
47 | + @user.block | |
46 | 48 | end |
47 | - @user.save! | |
49 | + | |
48 | 50 | @user |
49 | 51 | end |
50 | 52 | ... | ... |
lib/gitlab/graph/commit.rb
... | ... | @@ -1,52 +0,0 @@ |
1 | -require "grit" | |
2 | - | |
3 | -module Gitlab | |
4 | - module Graph | |
5 | - class Commit | |
6 | - include ActionView::Helpers::TagHelper | |
7 | - | |
8 | - attr_accessor :time, :space, :refs, :parent_spaces | |
9 | - | |
10 | - def initialize(commit) | |
11 | - @_commit = commit | |
12 | - @time = -1 | |
13 | - @space = 0 | |
14 | - @parent_spaces = [] | |
15 | - end | |
16 | - | |
17 | - def method_missing(m, *args, &block) | |
18 | - @_commit.send(m, *args, &block) | |
19 | - end | |
20 | - | |
21 | - def to_graph_hash | |
22 | - h = {} | |
23 | - h[:parents] = self.parents.collect do |p| | |
24 | - [p.id,0,0] | |
25 | - end | |
26 | - h[:author] = { | |
27 | - name: author.name, | |
28 | - email: author.email | |
29 | - } | |
30 | - h[:time] = time | |
31 | - h[:space] = space | |
32 | - h[:parent_spaces] = parent_spaces | |
33 | - h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? | |
34 | - h[:id] = sha | |
35 | - h[:date] = date | |
36 | - h[:message] = message | |
37 | - h | |
38 | - end | |
39 | - | |
40 | - def add_refs(ref_cache, repo) | |
41 | - if ref_cache.empty? | |
42 | - repo.refs.each do |ref| | |
43 | - ref_cache[ref.commit.id] ||= [] | |
44 | - ref_cache[ref.commit.id] << ref | |
45 | - end | |
46 | - end | |
47 | - @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id) | |
48 | - @refs ||= [] | |
49 | - end | |
50 | - end | |
51 | - end | |
52 | -end |
lib/gitlab/graph/json_builder.rb
... | ... | @@ -1,268 +0,0 @@ |
1 | -require "grit" | |
2 | - | |
3 | -module Gitlab | |
4 | - module Graph | |
5 | - class JsonBuilder | |
6 | - attr_accessor :days, :commits, :ref_cache, :repo | |
7 | - | |
8 | - def self.max_count | |
9 | - @max_count ||= 650 | |
10 | - end | |
11 | - | |
12 | - def initialize project, ref, commit | |
13 | - @project = project | |
14 | - @ref = ref | |
15 | - @commit = commit | |
16 | - @repo = project.repo | |
17 | - @ref_cache = {} | |
18 | - | |
19 | - @commits = collect_commits | |
20 | - @days = index_commits | |
21 | - end | |
22 | - | |
23 | - def to_json(*args) | |
24 | - { | |
25 | - days: @days.compact.map { |d| [d.day, d.strftime("%b")] }, | |
26 | - commits: @commits.map(&:to_graph_hash) | |
27 | - }.to_json(*args) | |
28 | - end | |
29 | - | |
30 | - protected | |
31 | - | |
32 | - # Get commits from repository | |
33 | - # | |
34 | - def collect_commits | |
35 | - | |
36 | - @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup | |
37 | - | |
38 | - # Decorate with app/models/commit.rb | |
39 | - @commits.map! { |commit| ::Commit.new(commit) } | |
40 | - | |
41 | - # Decorate with lib/gitlab/graph/commit.rb | |
42 | - @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) } | |
43 | - | |
44 | - # add refs to each commit | |
45 | - @commits.each { |commit| commit.add_refs(ref_cache, repo) } | |
46 | - | |
47 | - @commits | |
48 | - end | |
49 | - | |
50 | - # Method is adding time and space on the | |
51 | - # list of commits. As well as returns date list | |
52 | - # corelated with time set on commits. | |
53 | - # | |
54 | - # @param [Array<Graph::Commit>] commits to index | |
55 | - # | |
56 | - # @return [Array<TimeDate>] list of commit dates corelated with time on commits | |
57 | - def index_commits | |
58 | - days, times = [], [] | |
59 | - map = {} | |
60 | - | |
61 | - commits.reverse.each_with_index do |c,i| | |
62 | - c.time = i | |
63 | - days[i] = c.committed_date | |
64 | - map[c.id] = c | |
65 | - times[i] = c | |
66 | - end | |
67 | - | |
68 | - @_reserved = {} | |
69 | - days.each_index do |i| | |
70 | - @_reserved[i] = [] | |
71 | - end | |
72 | - | |
73 | - commits_sort_by_ref.each do |commit| | |
74 | - if map.include? commit.id then | |
75 | - place_chain(map[commit.id], map) | |
76 | - end | |
77 | - end | |
78 | - | |
79 | - # find parent spaces for not overlap lines | |
80 | - times.each do |c| | |
81 | - c.parent_spaces.concat(find_free_parent_spaces(c, map, times)) | |
82 | - end | |
83 | - | |
84 | - days | |
85 | - end | |
86 | - | |
87 | - # Skip count that the target commit is displayed in center. | |
88 | - def to_commit | |
89 | - commits = Grit::Commit.find_all(repo, nil, {topo_order: true}) | |
90 | - commit_index = commits.index do |c| | |
91 | - c.id == @commit.id | |
92 | - end | |
93 | - | |
94 | - if commit_index && (self.class.max_count / 2 < commit_index) then | |
95 | - # get max index that commit is displayed in the center. | |
96 | - commit_index - self.class.max_count / 2 | |
97 | - else | |
98 | - 0 | |
99 | - end | |
100 | - end | |
101 | - | |
102 | - def commits_sort_by_ref | |
103 | - commits.sort do |a,b| | |
104 | - if include_ref?(a) | |
105 | - -1 | |
106 | - elsif include_ref?(b) | |
107 | - 1 | |
108 | - else | |
109 | - b.committed_date <=> a.committed_date | |
110 | - end | |
111 | - end | |
112 | - end | |
113 | - | |
114 | - def include_ref?(commit) | |
115 | - heads = commit.refs.select do |ref| | |
116 | - ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag) | |
117 | - end | |
118 | - | |
119 | - heads.map! do |head| | |
120 | - head.name | |
121 | - end | |
122 | - | |
123 | - heads.include?(@ref) | |
124 | - end | |
125 | - | |
126 | - def find_free_parent_spaces(commit, map, times) | |
127 | - spaces = [] | |
128 | - | |
129 | - commit.parents.each do |p| | |
130 | - if map.include?(p.id) then | |
131 | - parent = map[p.id] | |
132 | - | |
133 | - range = if commit.time < parent.time then | |
134 | - commit.time..parent.time | |
135 | - else | |
136 | - parent.time..commit.time | |
137 | - end | |
138 | - | |
139 | - space = if commit.space >= parent.space then | |
140 | - find_free_parent_space(range, parent.space, 1, commit.space, times) | |
141 | - else | |
142 | - find_free_parent_space(range, parent.space, -1, parent.space, times) | |
143 | - end | |
144 | - | |
145 | - mark_reserved(range, space) | |
146 | - spaces << space | |
147 | - end | |
148 | - end | |
149 | - | |
150 | - spaces | |
151 | - end | |
152 | - | |
153 | - def find_free_parent_space(range, space_base, space_step, space_default, times) | |
154 | - if is_overlap?(range, times, space_default) then | |
155 | - find_free_space(range, space_base, space_step) | |
156 | - else | |
157 | - space_default | |
158 | - end | |
159 | - end | |
160 | - | |
161 | - def is_overlap?(range, times, overlap_space) | |
162 | - range.each do |i| | |
163 | - if i != range.first && | |
164 | - i != range.last && | |
165 | - times[i].space == overlap_space then | |
166 | - | |
167 | - return true; | |
168 | - end | |
169 | - end | |
170 | - | |
171 | - false | |
172 | - end | |
173 | - | |
174 | - # Add space mark on commit and its parents | |
175 | - # | |
176 | - # @param [Graph::Commit] the commit object. | |
177 | - # @param [Hash<String,Graph::Commit>] map of commits | |
178 | - def place_chain(commit, map, parent_time = nil) | |
179 | - leaves = take_left_leaves(commit, map) | |
180 | - if leaves.empty? | |
181 | - return | |
182 | - end | |
183 | - # and mark it as reserved | |
184 | - min_time = leaves.last.time | |
185 | - max_space = 1 | |
186 | - parents = leaves.last.parents.collect | |
187 | - parents.each do |p| | |
188 | - if map.include? p.id | |
189 | - parent = map[p.id] | |
190 | - if parent.time < min_time | |
191 | - min_time = parent.time | |
192 | - end | |
193 | - if max_space < parent.space then | |
194 | - max_space = parent.space | |
195 | - end | |
196 | - end | |
197 | - end | |
198 | - if parent_time.nil? | |
199 | - max_time = leaves.first.time | |
200 | - else | |
201 | - max_time = parent_time - 1 | |
202 | - end | |
203 | - | |
204 | - time_range = leaves.last.time..leaves.first.time | |
205 | - space = find_free_space(time_range, max_space, 2) | |
206 | - leaves.each{|l| l.space = space} | |
207 | - | |
208 | - mark_reserved(min_time..max_time, space) | |
209 | - | |
210 | - # Visit branching chains | |
211 | - leaves.each do |l| | |
212 | - parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?} | |
213 | - for p in parents | |
214 | - place_chain(map[p.id], map, l.time) | |
215 | - end | |
216 | - end | |
217 | - end | |
218 | - | |
219 | - def mark_reserved(time_range, space) | |
220 | - for day in time_range | |
221 | - @_reserved[day].push(space) | |
222 | - end | |
223 | - end | |
224 | - | |
225 | - def find_free_space(time_range, space_base, space_step) | |
226 | - reserved = [] | |
227 | - for day in time_range | |
228 | - reserved += @_reserved[day] | |
229 | - end | |
230 | - reserved.uniq! | |
231 | - | |
232 | - space = space_base | |
233 | - while reserved.include?(space) do | |
234 | - space += space_step | |
235 | - if space <= 0 then | |
236 | - space_step *= -1 | |
237 | - space = space_base + space_step | |
238 | - end | |
239 | - end | |
240 | - | |
241 | - space | |
242 | - end | |
243 | - | |
244 | - # Takes most left subtree branch of commits | |
245 | - # which don't have space mark yet. | |
246 | - # | |
247 | - # @param [Graph::Commit] the commit object. | |
248 | - # @param [Hash<String,Graph::Commit>] map of commits | |
249 | - # | |
250 | - # @return [Array<Graph::Commit>] list of branch commits | |
251 | - def take_left_leaves(commit, map) | |
252 | - leaves = [] | |
253 | - leaves.push(commit) if commit.space.zero? | |
254 | - | |
255 | - while true | |
256 | - return leaves if commit.parents.count.zero? | |
257 | - return leaves unless map.include? commit.parents.first.id | |
258 | - | |
259 | - commit = map[commit.parents.first.id] | |
260 | - | |
261 | - return leaves unless commit.space.zero? | |
262 | - | |
263 | - leaves.push(commit) | |
264 | - end | |
265 | - end | |
266 | - end | |
267 | - end | |
268 | -end |
lib/gitlab/markdown.rb
... | ... | @@ -25,6 +25,8 @@ module Gitlab |
25 | 25 | # >> gfm(":trollface:") |
26 | 26 | # => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" /> |
27 | 27 | module Markdown |
28 | + include IssuesHelper | |
29 | + | |
28 | 30 | attr_reader :html_options |
29 | 31 | |
30 | 32 | # Public: Parse the provided text with GitLab-Flavored Markdown |
... | ... | @@ -163,8 +165,11 @@ module Gitlab |
163 | 165 | end |
164 | 166 | |
165 | 167 | def reference_issue(identifier) |
166 | - if issue = @project.issues.where(id: identifier).first | |
167 | - link_to("##{identifier}", project_issue_url(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}")) | |
168 | + if @project.issue_exists? identifier | |
169 | + url = url_for_issue(identifier) | |
170 | + title = title_for_issue(identifier) | |
171 | + | |
172 | + link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}")) | |
168 | 173 | end |
169 | 174 | end |
170 | 175 | ... | ... |
lib/tasks/sidekiq.rake
1 | 1 | namespace :sidekiq do |
2 | 2 | desc "GITLAB | Stop sidekiq" |
3 | 3 | task :stop do |
4 | - run "bundle exec sidekiqctl stop #{pidfile}" | |
4 | + system "bundle exec sidekiqctl stop #{pidfile}" | |
5 | 5 | end |
6 | 6 | |
7 | 7 | desc "GITLAB | Start sidekiq" |
8 | 8 | task :start do |
9 | - run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &" | |
9 | + system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &" | |
10 | 10 | end |
11 | - | |
11 | + | |
12 | 12 | desc "GITLAB | Start sidekiq with launchd on Mac OS X" |
13 | 13 | task :launchd do |
14 | - run "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1" | |
14 | + system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1" | |
15 | 15 | end |
16 | - | |
16 | + | |
17 | 17 | def pidfile |
18 | 18 | Rails.root.join("tmp", "pids", "sidekiq.pid") |
19 | 19 | end | ... | ... |
spec/factories.rb
... | ... | @@ -29,6 +29,11 @@ FactoryGirl.define do |
29 | 29 | creator |
30 | 30 | end |
31 | 31 | |
32 | + factory :redmine_project, parent: :project do | |
33 | + issues_tracker { "redmine" } | |
34 | + issues_tracker_id { "project_name_in_redmine" } | |
35 | + end | |
36 | + | |
32 | 37 | factory :group do |
33 | 38 | sequence(:name) { |n| "group#{n}" } |
34 | 39 | path { name.downcase.gsub(/\s/, '_') } | ... | ... |
spec/factories/user_teams.rb
spec/features/admin/admin_users_spec.rb
... | ... | @@ -55,8 +55,8 @@ describe "Admin::Users" do |
55 | 55 | user = User.last |
56 | 56 | email = ActionMailer::Base.deliveries.last |
57 | 57 | email.subject.should have_content("Account was created") |
58 | - email.body.should have_content(user.email) | |
59 | - email.body.should have_content(@password) | |
58 | + email.text_part.body.should have_content(user.email) | |
59 | + email.text_part.body.should have_content(@password) | |
60 | 60 | end |
61 | 61 | end |
62 | 62 | |
... | ... | @@ -67,8 +67,8 @@ describe "Admin::Users" do |
67 | 67 | user = User.last |
68 | 68 | email = ActionMailer::Base.deliveries.last |
69 | 69 | email.subject.should have_content("Account was created") |
70 | - email.body.should have_content(user.email) | |
71 | - email.body.should_not have_content(@password) | |
70 | + email.text_part.body.should have_content(user.email) | |
71 | + email.text_part.body.should_not have_content(@password) | |
72 | 72 | end |
73 | 73 | end |
74 | 74 | end | ... | ... |
spec/helpers/gitlab_markdown_helper_spec.rb
... | ... | @@ -0,0 +1,79 @@ |
1 | +require "spec_helper" | |
2 | + | |
3 | +describe IssuesHelper do | |
4 | + let(:project) { create :project } | |
5 | + let(:issue) { create :issue, project: project } | |
6 | + let(:ext_project) { create :redmine_project } | |
7 | + | |
8 | + describe :title_for_issue do | |
9 | + it "should return issue title if used internal tracker" do | |
10 | + @project = project | |
11 | + title_for_issue(issue.id).should eq issue.title | |
12 | + end | |
13 | + | |
14 | + it "should always return empty string if used external tracker" do | |
15 | + @project = ext_project | |
16 | + title_for_issue(rand(100)).should eq "" | |
17 | + end | |
18 | + | |
19 | + it "should always return empty string if project nil" do | |
20 | + @project = nil | |
21 | + | |
22 | + title_for_issue(rand(100)).should eq "" | |
23 | + end | |
24 | + end | |
25 | + | |
26 | + describe :url_for_project_issues do | |
27 | + let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url} | |
28 | + let(:ext_expected) do | |
29 | + project_url.gsub(':project_id', ext_project.id.to_s) | |
30 | + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) | |
31 | + end | |
32 | + let(:int_expected) { polymorphic_path([project]) } | |
33 | + | |
34 | + it "should return internal path if used internal tracker" do | |
35 | + @project = project | |
36 | + url_for_project_issues.should match(int_expected) | |
37 | + end | |
38 | + | |
39 | + it "should return path to external tracker" do | |
40 | + @project = ext_project | |
41 | + | |
42 | + url_for_project_issues.should match(ext_expected) | |
43 | + end | |
44 | + | |
45 | + it "should return empty string if project nil" do | |
46 | + @project = nil | |
47 | + | |
48 | + url_for_project_issues.should eq "" | |
49 | + end | |
50 | + end | |
51 | + | |
52 | + describe :url_for_issue do | |
53 | + let(:issue_id) { 3 } | |
54 | + let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url} | |
55 | + let(:ext_expected) do | |
56 | + issues_url.gsub(':id', issue_id.to_s) | |
57 | + .gsub(':project_id', ext_project.id.to_s) | |
58 | + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) | |
59 | + end | |
60 | + let(:int_expected) { polymorphic_path([project, issue]) } | |
61 | + | |
62 | + it "should return internal path if used internal tracker" do | |
63 | + @project = project | |
64 | + url_for_issue(issue.id).should match(int_expected) | |
65 | + end | |
66 | + | |
67 | + it "should return path to external tracker" do | |
68 | + @project = ext_project | |
69 | + | |
70 | + url_for_issue(issue_id).should match(ext_expected) | |
71 | + end | |
72 | + | |
73 | + it "should return empty string if project nil" do | |
74 | + @project = nil | |
75 | + | |
76 | + url_for_issue(issue.id).should eq "" | |
77 | + end | |
78 | + end | |
79 | +end | ... | ... |
spec/models/project_spec.rb
... | ... | @@ -60,6 +60,7 @@ describe Project do |
60 | 60 | it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) } |
61 | 61 | it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) } |
62 | 62 | it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) } |
63 | + it { should ensure_length_of(:issues_tracker_id).is_within(0..255) } | |
63 | 64 | |
64 | 65 | it "should not allow new projects beyond user limits" do |
65 | 66 | project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1)) |
... | ... | @@ -190,4 +191,57 @@ describe Project do |
190 | 191 | Project.new(path: "empty").repository.should be_nil |
191 | 192 | end |
192 | 193 | end |
194 | + | |
195 | + describe :issue_exists? do | |
196 | + let(:project) { create(:project) } | |
197 | + let(:existed_issue) { create(:issue, project: project) } | |
198 | + let(:not_existed_issue) { create(:issue) } | |
199 | + let(:ext_project) { create(:redmine_project) } | |
200 | + | |
201 | + it "should be true or if used internal tracker and issue exists" do | |
202 | + project.issue_exists?(existed_issue.id).should be_true | |
203 | + end | |
204 | + | |
205 | + it "should be false or if used internal tracker and issue not exists" do | |
206 | + project.issue_exists?(not_existed_issue.id).should be_false | |
207 | + end | |
208 | + | |
209 | + it "should always be true if used other tracker" do | |
210 | + ext_project.issue_exists?(rand(100)).should be_true | |
211 | + end | |
212 | + end | |
213 | + | |
214 | + describe :used_default_issues_tracker? do | |
215 | + let(:project) { create(:project) } | |
216 | + let(:ext_project) { create(:redmine_project) } | |
217 | + | |
218 | + it "should be true if used internal tracker" do | |
219 | + project.used_default_issues_tracker?.should be_true | |
220 | + end | |
221 | + | |
222 | + it "should be false if used other tracker" do | |
223 | + ext_project.used_default_issues_tracker?.should be_false | |
224 | + end | |
225 | + end | |
226 | + | |
227 | + describe :can_have_issues_tracker_id? do | |
228 | + let(:project) { create(:project) } | |
229 | + let(:ext_project) { create(:redmine_project) } | |
230 | + | |
231 | + it "should be true for projects with external issues tracker if issues enabled" do | |
232 | + ext_project.can_have_issues_tracker_id?.should be_true | |
233 | + end | |
234 | + | |
235 | + it "should be false for projects with internal issue tracker if issues enabled" do | |
236 | + project.can_have_issues_tracker_id?.should be_false | |
237 | + end | |
238 | + | |
239 | + it "should be always false if issues disbled" do | |
240 | + project.issues_enabled = false | |
241 | + ext_project.issues_enabled = false | |
242 | + | |
243 | + project.can_have_issues_tracker_id?.should be_false | |
244 | + ext_project.can_have_issues_tracker_id?.should be_false | |
245 | + end | |
246 | + end | |
193 | 247 | end | ... | ... |
spec/models/user_spec.rb
... | ... | @@ -25,7 +25,7 @@ |
25 | 25 | # dark_scheme :boolean default(FALSE), not null |
26 | 26 | # theme_id :integer default(1), not null |
27 | 27 | # bio :string(255) |
28 | -# blocked :boolean default(FALSE), not null | |
28 | +# state :string(255) default(FALSE), not null | |
29 | 29 | # failed_attempts :integer default(0) |
30 | 30 | # locked_at :datetime |
31 | 31 | # extern_uid :string(255) |
... | ... | @@ -140,7 +140,7 @@ describe User do |
140 | 140 | |
141 | 141 | it "should block user" do |
142 | 142 | user.block |
143 | - user.blocked.should be_true | |
143 | + user.blocked?.should be_true | |
144 | 144 | end |
145 | 145 | end |
146 | 146 | |
... | ... | @@ -149,7 +149,7 @@ describe User do |
149 | 149 | User.delete_all |
150 | 150 | @user = create :user |
151 | 151 | @admin = create :user, admin: true |
152 | - @blocked = create :user, blocked: true | |
152 | + @blocked = create :user, state: :blocked | |
153 | 153 | end |
154 | 154 | |
155 | 155 | it { User.filter("admins").should == [@admin] } | ... | ... |
spec/observers/user_observer_spec.rb
... | ... | @@ -15,7 +15,13 @@ describe UserObserver do |
15 | 15 | create(:user) |
16 | 16 | end |
17 | 17 | |
18 | + it 'no email for external' do | |
19 | + Notify.should_not_receive(:new_user_email) | |
20 | + create(:user, extern_uid: '32442eEfsafada') | |
21 | + end | |
22 | + | |
18 | 23 | it 'trigger logger' do |
24 | + user = double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local', extern_uid?: false) | |
19 | 25 | Gitlab::AppLogger.should_receive(:info) |
20 | 26 | create(:user) |
21 | 27 | end | ... | ... |
vendor/assets/javascripts/branch-graph.js
... | ... | @@ -1,385 +0,0 @@ |
1 | -!function(){ | |
2 | - | |
3 | - var BranchGraph = function(element, options){ | |
4 | - this.element = element; | |
5 | - this.options = options; | |
6 | - | |
7 | - this.preparedCommits = {}; | |
8 | - this.mtime = 0; | |
9 | - this.mspace = 0; | |
10 | - this.parents = {}; | |
11 | - this.colors = ["#000"]; | |
12 | - | |
13 | - this.load(); | |
14 | - }; | |
15 | - | |
16 | - BranchGraph.prototype.load = function(){ | |
17 | - $.ajax({ | |
18 | - url: this.options.url, | |
19 | - method: 'get', | |
20 | - dataType: 'json', | |
21 | - success: $.proxy(function(data){ | |
22 | - $('.loading', this.element).hide(); | |
23 | - this.prepareData(data.days, data.commits); | |
24 | - this.buildGraph(); | |
25 | - }, this) | |
26 | - }); | |
27 | - }; | |
28 | - | |
29 | - BranchGraph.prototype.prepareData = function(days, commits){ | |
30 | - this.days = days; | |
31 | - this.dayCount = days.length; | |
32 | - this.commits = commits; | |
33 | - this.commitCount = commits.length; | |
34 | - | |
35 | - this.collectParents(); | |
36 | - | |
37 | - this.mtime += 4; | |
38 | - this.mspace += 10; | |
39 | - for (var i = 0; i < this.commitCount; i++) { | |
40 | - if (this.commits[i].id in this.parents) { | |
41 | - this.commits[i].isParent = true; | |
42 | - } | |
43 | - this.preparedCommits[this.commits[i].id] = this.commits[i]; | |
44 | - } | |
45 | - this.collectColors(); | |
46 | - }; | |
47 | - | |
48 | - BranchGraph.prototype.collectParents = function(){ | |
49 | - for (var i = 0; i < this.commitCount; i++) { | |
50 | - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { | |
51 | - this.parents[this.commits[i].parents[j][0]] = true; | |
52 | - } | |
53 | - this.mtime = Math.max(this.mtime, this.commits[i].time); | |
54 | - this.mspace = Math.max(this.mspace, this.commits[i].space); | |
55 | - } | |
56 | - }; | |
57 | - | |
58 | - BranchGraph.prototype.collectColors = function(){ | |
59 | - for (var k = 0; k < this.mspace; k++) { | |
60 | - this.colors.push(Raphael.getColor(.8)); | |
61 | - // Skipping a few colors in the spectrum to get more contrast between colors | |
62 | - Raphael.getColor();Raphael.getColor(); | |
63 | - } | |
64 | - }; | |
65 | - | |
66 | - BranchGraph.prototype.buildGraph = function(){ | |
67 | - var graphWidth = $(this.element).width() | |
68 | - , ch = this.mspace * 20 + 100 | |
69 | - , cw = Math.max(graphWidth, this.mtime * 20 + 260) | |
70 | - , r = Raphael(this.element.get(0), cw, ch) | |
71 | - , top = r.set() | |
72 | - , cuday = 0 | |
73 | - , cumonth = "" | |
74 | - , offsetX = 20 | |
75 | - , offsetY = 60 | |
76 | - , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320) | |
77 | - , scrollLeft = cw; | |
78 | - | |
79 | - this.raphael = r; | |
80 | - | |
81 | - r.rect(0, 0, barWidth, 20).attr({fill: "#222"}); | |
82 | - r.rect(0, 20, barWidth, 20).attr({fill: "#444"}); | |
83 | - | |
84 | - for (mm = 0; mm < this.dayCount; mm++) { | |
85 | - if(this.days[mm] != null){ | |
86 | - if(cuday != this.days[mm][0]){ | |
87 | - // Dates | |
88 | - r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({ | |
89 | - font: "12px Monaco, monospace", | |
90 | - fill: "#DDD" | |
91 | - }); | |
92 | - cuday = this.days[mm][0]; | |
93 | - } | |
94 | - if(cumonth != this.days[mm][1]){ | |
95 | - // Months | |
96 | - r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({ | |
97 | - font: "12px Monaco, monospace", | |
98 | - fill: "#EEE" | |
99 | - }); | |
100 | - cumonth = this.days[mm][1]; | |
101 | - } | |
102 | - } | |
103 | - } | |
104 | - | |
105 | - for (i = 0; i < this.commitCount; i++) { | |
106 | - var x = offsetX + 20 * this.commits[i].time | |
107 | - , y = offsetY + 10 * this.commits[i].space | |
108 | - , c | |
109 | - , ps; | |
110 | - | |
111 | - // Draw dot | |
112 | - r.circle(x, y, 3).attr({ | |
113 | - fill: this.colors[this.commits[i].space], | |
114 | - stroke: "none" | |
115 | - }); | |
116 | - | |
117 | - // Draw lines | |
118 | - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { | |
119 | - c = this.preparedCommits[this.commits[i].parents[j][0]]; | |
120 | - ps = this.commits[i].parent_spaces[j]; | |
121 | - if (c) { | |
122 | - var cx = offsetX + 20 * c.time | |
123 | - , cy = offsetY + 10 * c.space | |
124 | - , psy = offsetY + 10 * ps; | |
125 | - if (c.space == this.commits[i].space && c.space == ps) { | |
126 | - r.path([ | |
127 | - "M", x, y, | |
128 | - "L", cx, cy | |
129 | - ]).attr({ | |
130 | - stroke: this.colors[c.space], | |
131 | - "stroke-width": 2 | |
132 | - }); | |
133 | - | |
134 | - } else if (c.space < this.commits[i].space) { | |
135 | - r.path([ | |
136 | - "M", x - 5, y, | |
137 | - "l-5-2,0,4,5,-2", | |
138 | - "L", x - 10, y, | |
139 | - "L", x - 15, psy, | |
140 | - "L", cx + 5, psy, | |
141 | - "L", cx, cy]) | |
142 | - .attr({ | |
143 | - stroke: this.colors[this.commits[i].space], | |
144 | - "stroke-width": 2 | |
145 | - }); | |
146 | - } else { | |
147 | - r.path([ | |
148 | - "M", x - 3, y + 6, | |
149 | - "l-4,3,4,2,0,-5", | |
150 | - "L", x - 5, y + 10, | |
151 | - "L", x - 10, psy, | |
152 | - "L", cx + 5, psy, | |
153 | - "L", cx, cy]) | |
154 | - .attr({ | |
155 | - stroke: this.colors[c.space], | |
156 | - "stroke-width": 2 | |
157 | - }); | |
158 | - } | |
159 | - } | |
160 | - } | |
161 | - | |
162 | - if (this.commits[i].refs) { | |
163 | - this.appendLabel(x, y, this.commits[i].refs); | |
164 | - } | |
165 | - | |
166 | - // mark commit and displayed in the center | |
167 | - if (this.commits[i].id == this.options.commit_id) { | |
168 | - r.path([ | |
169 | - 'M', x, y - 5, | |
170 | - 'L', x + 4, y - 15, | |
171 | - 'L', x - 4, y - 15, | |
172 | - 'Z' | |
173 | - ]).attr({ | |
174 | - "fill": "#000", | |
175 | - "fill-opacity": .7, | |
176 | - "stroke": "none" | |
177 | - }); | |
178 | - scrollLeft = x - graphWidth / 2; | |
179 | - } | |
180 | - | |
181 | - this.appendAnchor(top, this.commits[i], x, y); | |
182 | - } | |
183 | - top.toFront(); | |
184 | - this.element.scrollLeft(scrollLeft); | |
185 | - this.bindEvents(); | |
186 | - }; | |
187 | - | |
188 | - BranchGraph.prototype.bindEvents = function(){ | |
189 | - var drag = {} | |
190 | - , element = this.element; | |
191 | - | |
192 | - var dragger = function(event){ | |
193 | - element.scrollLeft(drag.sl - (event.clientX - drag.x)); | |
194 | - element.scrollTop(drag.st - (event.clientY - drag.y)); | |
195 | - }; | |
196 | - | |
197 | - element.on({ | |
198 | - mousedown: function (event) { | |
199 | - drag = { | |
200 | - x: event.clientX, | |
201 | - y: event.clientY, | |
202 | - st: element.scrollTop(), | |
203 | - sl: element.scrollLeft() | |
204 | - }; | |
205 | - $(window).on('mousemove', dragger); | |
206 | - } | |
207 | - }); | |
208 | - $(window).on({ | |
209 | - mouseup: function(){ | |
210 | - //bars.animate({opacity: 0}, 300); | |
211 | - $(window).off('mousemove', dragger); | |
212 | - }, | |
213 | - keydown: function(event){ | |
214 | - if(event.keyCode == 37){ | |
215 | - // left | |
216 | - element.scrollLeft( element.scrollLeft() - 50); | |
217 | - } | |
218 | - if(event.keyCode == 38){ | |
219 | - // top | |
220 | - element.scrollTop( element.scrollTop() - 50); | |
221 | - } | |
222 | - if(event.keyCode == 39){ | |
223 | - // right | |
224 | - element.scrollLeft( element.scrollLeft() + 50); | |
225 | - } | |
226 | - if(event.keyCode == 40){ | |
227 | - // bottom | |
228 | - element.scrollTop( element.scrollTop() + 50); | |
229 | - } | |
230 | - } | |
231 | - }); | |
232 | - }; | |
233 | - | |
234 | - BranchGraph.prototype.appendLabel = function(x, y, refs){ | |
235 | - var r = this.raphael | |
236 | - , shortrefs = refs | |
237 | - , text, textbox, rect; | |
238 | - | |
239 | - if (shortrefs.length > 17){ | |
240 | - // Truncate if longer than 15 chars | |
241 | - shortrefs = shortrefs.substr(0,15) + "…"; | |
242 | - } | |
243 | - | |
244 | - text = r.text(x+5, y+8 + 10, shortrefs).attr({ | |
245 | - font: "10px Monaco, monospace", | |
246 | - fill: "#FFF", | |
247 | - title: refs | |
248 | - }); | |
249 | - | |
250 | - textbox = text.getBBox(); | |
251 | - text.transform([ | |
252 | - 't', textbox.height/-4, textbox.width/2 + 5, | |
253 | - 'r90' | |
254 | - ]); | |
255 | - | |
256 | - // Create rectangle based on the size of the textbox | |
257 | - rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({ | |
258 | - "fill": "#000", | |
259 | - "fill-opacity": .7, | |
260 | - "stroke": "none" | |
261 | - }); | |
262 | - | |
263 | - triangle = r.path([ | |
264 | - 'M', x, y + 5, | |
265 | - 'L', x + 4, y + 15, | |
266 | - 'L', x - 4, y + 15, | |
267 | - 'Z' | |
268 | - ]).attr({ | |
269 | - "fill": "#000", | |
270 | - "fill-opacity": .7, | |
271 | - "stroke": "none" | |
272 | - }); | |
273 | - | |
274 | - // Rotate and reposition rectangle over text | |
275 | - rect.transform([ | |
276 | - 'r', 90, x, y, | |
277 | - 't', 15, -9 | |
278 | - ]); | |
279 | - | |
280 | - // Set text to front | |
281 | - text.toFront(); | |
282 | - }; | |
283 | - | |
284 | - BranchGraph.prototype.appendAnchor = function(top, commit, x, y) { | |
285 | - var r = this.raphael | |
286 | - , options = this.options | |
287 | - , anchor; | |
288 | - anchor = r.circle(x, y, 10).attr({ | |
289 | - fill: "#000", | |
290 | - opacity: 0, | |
291 | - cursor: "pointer" | |
292 | - }) | |
293 | - .click(function(){ | |
294 | - window.open(options.commit_url.replace('%s', commit.id), '_blank'); | |
295 | - }) | |
296 | - .hover(function(){ | |
297 | - this.tooltip = r.commitTooltip(x, y + 5, commit); | |
298 | - top.push(this.tooltip.insertBefore(this)); | |
299 | - }, function(){ | |
300 | - this.tooltip && this.tooltip.remove() && delete this.tooltip; | |
301 | - }); | |
302 | - top.push(anchor); | |
303 | - }; | |
304 | - | |
305 | - this.BranchGraph = BranchGraph; | |
306 | - | |
307 | -}(this); | |
308 | -Raphael.fn.commitTooltip = function(x, y, commit){ | |
309 | - var nameText, idText, messageText | |
310 | - , boxWidth = 300 | |
311 | - , boxHeight = 200; | |
312 | - | |
313 | - nameText = this.text(x, y + 10, commit.author.name); | |
314 | - idText = this.text(x, y + 35, commit.id); | |
315 | - messageText = this.text(x, y + 50, commit.message); | |
316 | - | |
317 | - textSet = this.set(nameText, idText, messageText).attr({ | |
318 | - "text-anchor": "start", | |
319 | - "font": "12px Monaco, monospace" | |
320 | - }); | |
321 | - | |
322 | - nameText.attr({ | |
323 | - "font": "14px Arial", | |
324 | - "font-weight": "bold" | |
325 | - }); | |
326 | - | |
327 | - idText.attr({ | |
328 | - "fill": "#AAA" | |
329 | - }); | |
330 | - | |
331 | - textWrap(messageText, boxWidth - 50); | |
332 | - | |
333 | - var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ | |
334 | - "fill": "#FFF", | |
335 | - "stroke": "#000", | |
336 | - "stroke-linecap": "round", | |
337 | - "stroke-width": 2 | |
338 | - }); | |
339 | - var tooltip = this.set(rect, textSet); | |
340 | - | |
341 | - rect.attr({ | |
342 | - "height" : tooltip.getBBox().height + 10, | |
343 | - "width" : tooltip.getBBox().width + 10 | |
344 | - }); | |
345 | - | |
346 | - tooltip.transform([ | |
347 | - 't', 20, 20 | |
348 | - ]); | |
349 | - | |
350 | - return tooltip; | |
351 | -}; | |
352 | - | |
353 | -function textWrap(t, width) { | |
354 | - var content = t.attr("text"); | |
355 | - var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
356 | - t.attr({ | |
357 | - "text" : abc | |
358 | - }); | |
359 | - var letterWidth = t.getBBox().width / abc.length; | |
360 | - | |
361 | - t.attr({ | |
362 | - "text" : content | |
363 | - }); | |
364 | - | |
365 | - var words = content.split(" "); | |
366 | - var x = 0, s = []; | |
367 | - for ( var i = 0; i < words.length; i++) { | |
368 | - | |
369 | - var l = words[i].length; | |
370 | - if (x + (l * letterWidth) > width) { | |
371 | - s.push("\n"); | |
372 | - x = 0; | |
373 | - } | |
374 | - x += l * letterWidth; | |
375 | - s.push(words[i] + " "); | |
376 | - } | |
377 | - t.attr({ | |
378 | - "text" : s.join("") | |
379 | - }); | |
380 | - var b = t.getBBox() | |
381 | - , h = Math.abs(b.y2) - Math.abs(b.y) + 1; | |
382 | - t.attr({ | |
383 | - "y": b.y + h | |
384 | - }); | |
385 | -} |