Compare commits

...

191 Commits
3.4 ... 3.7

Author SHA1 Message Date
Naoki Takezoe
e8a1543466 Fix commit comment style 2015-10-03 13:28:59 +09:00
Naoki Takezoe
81c79003ec GitBucket 3.7.0 release 2015-10-03 13:13:48 +09:00
Naoki Takezoe
73cf9661ac Fix url encoding to encode whitespace to %20 2015-10-03 04:00:08 +09:00
Naoki Takezoe
6e994b0ae1 (refs #918)Fix pull request comment 2015-10-03 03:41:05 +09:00
Naoki Takezoe
c5da975cea Merge pull request #919 from team-lab/fix-push-commit-url
fix commit url in webhook `push` event.
2015-10-02 21:13:58 +09:00
Naoki Takezoe
1f3ef962e8 Merge pull request #921 from team-lab/fix-#700-update-pr-on-web-ui
(refs #700)web ui merge does not update pull request that already exist
2015-10-02 21:02:03 +09:00
Naoki Takezoe
f6066a0361 Merge pull request #920 from team-lab/fix-webhook-issue-comment-url
fix pull-request url on webhook payload
2015-10-02 20:45:23 +09:00
nazoking
ace65cf261 (refs #920) Remove IssueOrPullRequest. it is too much for this purpose 2015-10-02 11:59:25 +09:00
nazoking
1df537ce5c (refs #700)web ui merge does not update pull request that already exist 2015-10-02 00:49:11 +09:00
nazoking
bf64f6b4f4 api url is only issues 2015-10-02 00:33:23 +09:00
nazoking
0283ec574d fix pull-request url on webhook payload 2015-10-02 00:23:13 +09:00
nazoking
d566f64e8b The ApiCommit should be export url and html values in json. 2015-10-01 23:04:35 +09:00
nazoking
bb9add9da9 fix commit url in push webhook. 2015-10-01 22:56:36 +09:00
Naoki Takezoe
75d085a2c4 (refs #917)Fix incorrent HTML escape 2015-10-01 13:04:56 +09:00
Naoki Takezoe
4eab07ffaf Merge branch 'snowgooseyk-master' 2015-10-01 10:23:55 +09:00
Naoki Takezoe
cf7aaa25cd (refs #915)Fixup 2015-10-01 10:22:55 +09:00
Naoki Takezoe
8011b22de6 Merge branch 'master' of https://github.com/snowgooseyk/gitbucket into snowgooseyk-master 2015-10-01 10:16:21 +09:00
Naoki Takezoe
a108489d71 Update README.md 2015-09-30 03:25:43 +09:00
Naoki Takezoe
185c132771 Create CONTRIBUTING.md 2015-09-30 03:22:47 +09:00
Naoki Takezoe
3b11e905a1 Merge branch 'chazmuzz-master' 2015-09-30 02:30:44 +09:00
Naoki Takezoe
5a04fe7ae6 (refs #906)Shrink range which is applied style 2015-09-30 02:30:28 +09:00
Naoki Takezoe
92c73062cc Expatnd wiki page width 2015-09-29 01:20:28 +09:00
Naoki Takezoe
d07624bdc1 Sanitize in markdown 2015-09-29 01:20:10 +09:00
snowgooseyk
e1dbe80ccd Fix ArrayIndexOutOfBoundsException 2015-09-29 00:03:49 +09:00
Naoki Takezoe
6d69a52292 (refs #800)Introduce environment variable JAVA_OPTS to specify JVM options when build GitBucket 2015-09-24 13:13:36 +09:00
Naoki Takezoe
68af5479c8 (refs #823)Enable file finder for branches which contain / 2015-09-24 13:06:53 +09:00
Naoki Takezoe
3970eca8dc Merge branch 'McFoggy-issue-893' 2015-09-24 03:51:02 +09:00
Naoki Takezoe
b11d36c3a5 (refs #896)Add description and migration for separation of notification and SMTP configuration 2015-09-24 03:48:08 +09:00
Naoki Takezoe
3c53fd8618 Merge branch 'issue-893' of https://github.com/McFoggy/gitbucket into McFoggy-issue-893 2015-09-24 01:21:00 +09:00
Naoki Takezoe
4ca4c57fff Merge branch 'master' of https://github.com/chazmuzz/gitbucket into chazmuzz-master 2015-09-24 00:46:58 +09:00
Naoki Takezoe
317a6fde30 (refs #913)Remove Java8 dependency 2015-09-23 19:12:21 +09:00
Naoki Takezoe
ad08d385e6 Bump markedj 1.0.2 to Java7 support 2015-09-23 12:22:55 +09:00
Naoki Takezoe
805e12aceb Update README.md 2015-09-22 11:12:39 +09:00
Naoki Takezoe
3a45912400 Update README.md 2015-09-22 11:12:07 +09:00
Naoki Takezoe
19817e2659 Merge pull request #912 from takezoe/markedj
Switch markdown processor to markedj from pegdown
2015-09-22 11:04:14 +09:00
Naoki Takezoe
50dc205ef7 Use JDK8 for Travis build 2015-09-22 03:26:57 +09:00
Naoki Takezoe
2402a3ac72 Enable task list after update issue 2015-09-22 03:06:53 +09:00
Naoki Takezoe
e1c155d09d Fix Markdown testcase 2015-09-21 12:08:16 +09:00
Naoki Takezoe
84c3bc4ad4 Refactoring 2015-09-21 11:24:59 +09:00
Naoki Takezoe
353784c23e Restore header anchor 2015-09-21 11:01:40 +09:00
Naoki Takezoe
a359624f01 Restore task-list support 2015-09-21 10:48:44 +09:00
Naoki Takezoe
a0f684cfdf Bump markedj to 1.0.1 2015-09-20 14:39:20 +09:00
Naoki Takezoe
1ea1e74a0c Fix links 2015-09-20 12:49:49 +09:00
Naoki Takezoe
8f7c5fc922 Fix code and ordered list style 2015-09-20 04:45:46 +09:00
Naoki Takezoe
667ef680c1 Switch markdown processor to markedj from pegdown 2015-09-20 03:51:46 +09:00
Charlie Murray
972ab0df50 Truncate text inside a box-content-row div rather than flowing out of the box 2015-09-15 20:13:46 +01:00
Naoki Takezoe
bb2e77d899 Update README.md 2015-09-13 11:56:18 +09:00
Naoki Takezoe
a3daf13c15 Fix issue link in Markdown 2015-09-07 02:10:37 +09:00
Naoki Takezoe
fb2b2e37ce Remove last committer from the file list 2015-09-07 00:50:03 +09:00
Naoki Takezoe
c1381179aa Fix code style 2015-09-06 23:32:06 +09:00
Naoki Takezoe
9e2dc3f892 Merge pull request #897 from cd01/patch-1
Fix typo in README.md
2015-09-04 13:32:29 +09:00
Naoki Takezoe
5aa548d613 Update octicons 2015-09-03 22:45:52 +09:00
Naoki Takezoe
5225a95d3a Merge branch 'hikaruworld-SupportCloneInDesktop' 2015-09-03 22:31:38 +09:00
Naoki Takezoe
53b7a1fce5 Merge branch 'SupportCloneInDesktop' of https://github.com/hikaruworld/gitbucket into hikaruworld-SupportCloneInDesktop 2015-09-03 22:29:14 +09:00
Naoki Takezoe
02369a4949 (refs #895)Fix generate anchor option in Wiki 2015-09-03 21:25:56 +09:00
cd01
1ca548991b Fix typo in README.md 2015-09-03 21:20:09 +09:00
Matthieu Brouillard
0772070523 split SMTP & notification, fixes #893 2015-09-02 16:22:54 +02:00
Naoki Takezoe
4bf3848856 Merge pull request #894 from McFoggy/new-h2-backup-plugin
reference gitbucket-h2-backup-plugin in README
2015-09-02 01:56:04 +09:00
Matthieu Brouillard
512425de4c reference gitbucket-h2-backup-plugin in README 2015-09-01 14:36:59 +02:00
Naoki Takezoe
7f28bd6a26 Update README.md 2015-08-30 03:57:26 +09:00
Naoki Takezoe
4088b2c1e8 Exclude group name from issue / pull request assignees 2015-08-30 03:33:29 +09:00
Naoki Takezoe
919d55c002 Fix NullPointerException for non-existent branches 2015-08-27 11:16:33 +09:00
Naoki Takezoe
068bbd0c3b Merge branch 'officer-retry_fix_#838' 2015-08-26 22:48:03 +09:00
Naoki Takezoe
9f50528192 Merge branch 'retry_fix_#838' of https://github.com/officer/gitbucket into officer-retry_fix_#838
# Conflicts:
#	src/main/scala/gitbucket/core/view/LinkConverter.scala
2015-08-26 22:47:41 +09:00
Naoki Takezoe
4c149cf01c (refs #831)url encode filename in the redirect path 2015-08-26 22:17:21 +09:00
Naoki Takezoe
c86c706406 (refs #864)Fix blame 2015-08-24 02:40:29 +09:00
Naoki Takezoe
3b0a0f55b5 Fix broken blame 2015-08-24 02:09:50 +09:00
Naoki Takezoe
4232b8184e Change a limit of initial amount of the repositories list 2015-08-23 15:47:07 +09:00
Naoki Takezoe
e5f3dfe293 Update version to 3.6.0 2015-08-23 15:42:52 +09:00
Naoki Takezoe
22af94d36a BugFix and improvement for pull request 2015-08-23 13:44:12 +09:00
Naoki Takezoe
d6b6781861 Add show more link for repositories and wiki pages 2015-08-23 03:19:52 +09:00
Naoki Takezoe
2222299793 Fix merge checking 2015-08-23 02:17:46 +09:00
Naoki Takezoe
fdd9a184b5 Fix presentation of commit list in the pull request detail view 2015-08-22 17:20:59 +09:00
Naoki Takezoe
99492e3f8e Merge pull request #886 from noc06140728/fix-octicon
Replace some icon to octicon
2015-08-21 02:12:15 +09:00
Naoki Takezoe
a42c40bbc1 Fix merge guide to display ssh url 2015-08-21 02:11:42 +09:00
Masahiro Namba
2794f9fcfc Replace some icon to octicon
- replace some of the non-octicon to octicon.
- adjust the color of octicon on the button.

modified icon is as follows.

- .icon-home          -> .octicon-home
- .icon-time          -> .octicon-clock
- .icon-ok            -> .octicon-check
- .icon-lock          -> .octicon-lock
- .icon-envelope      -> .octicon-mail
- .icon-pencil        -> .octicon-pencil
- .icon-remove-circle -> .octicon-x
- .icon-check         -> .octicon-clippy
- .icon-calendar      -> .octicon-calendar
- .icon-cog           -> .octicon-gear
- .icon-th-list       -> .octicon-list-unordered
- .icon-trash         -> .octicon-trashcan
- .icon-arrow-right   -> .octicon-arrow-right
- .icon-retweet       -> .octicon-git-compare
- .icon-comment       -> .octicon-comment
2015-08-20 20:23:47 +09:00
Naoki Takezoe
28c0262e74 Improve issue and pull request creation form 2015-08-19 10:20:52 +09:00
Naoki Takezoe
8634191bd2 Merge pull request #884 from skx/master
Updated to fix truncated name in JSON: watchers_coun
2015-08-18 17:27:29 +09:00
Steve Kemp
f73c86d533 Updated to fix truncated name in JSON: watchers_coun
The correct field in the JSON should be `watchers_count` rather
than the truncated version `watchers_coun`.
2015-08-18 09:14:44 +03:00
Naoki Takezoe
f042d709ac Improve issue creation form 2015-08-18 10:42:16 +09:00
Naoki Takezoe
e2a6149a93 Update auto_update.md 2015-08-17 13:29:41 +09:00
Naoki Takezoe
b2a7e2c7e2 Merge pull request #882 from garygreen/shorten-commit-message
File listing and sidebar display improvements
2015-08-17 06:19:00 +09:00
Naoki Takezoe
89fc143075 Update merge guide 2015-08-16 23:43:57 +09:00
Gary Green
a754a92799 File listing and sidebar display improvements 2015-08-16 14:55:18 +01:00
Naoki Takezoe
dc26fcf609 Merge branch 'garygreen-link-width' 2015-08-16 11:07:08 +09:00
Naoki Takezoe
b9db57eeef Merge branch 'link-width' of https://github.com/garygreen/gitbucket into garygreen-link-width 2015-08-16 11:03:48 +09:00
Naoki Takezoe
9b377c727d Improve the pull request creation form 2015-08-16 02:30:13 +09:00
Naoki Takezoe
e5b8d81bb4 Remove unused code 2015-08-16 01:13:25 +09:00
Naoki Takezoe
c85b31a7d5 Improve comparing view 2015-08-16 01:12:44 +09:00
Naoki Takezoe
6580e5458a Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-08-16 00:55:18 +09:00
Naoki Takezoe
4e4e65eaa6 Improve th background color 2015-08-15 14:31:00 +09:00
Naoki Takezoe
9d19aad384 Merge pull request #880 from garygreen/issues-gui
Improved design of issue
2015-08-15 11:29:56 +09:00
Naoki Takezoe
c16a9f234b (refs #878)Hide the Delete button for other than the head of branch 2015-08-15 11:21:26 +09:00
Naoki Takezoe
ace551c33d (refs #871)Make link for @mention which contains dot 2015-08-15 10:15:44 +09:00
Naoki Takezoe
1e6e686692 Merge pull request #873 from superhj1987/master
Update main.scala.html
2015-08-15 09:59:16 +09:00
Gary Green
afdcc3f7c0 Improved design of issue 2015-08-15 00:12:56 +01:00
Naoki Takezoe
00e64bc46c Remove IntelliJ specific file 2015-08-13 09:24:18 +09:00
Bryant Hang
a959e1820f Update main.scala.html
fix header plus dropdown menu display bug in safari and add 'Your profile' in user dropdown menu
2015-08-12 15:39:54 +08:00
Naoki Takezoe
3dfbdbfe51 (refs #865)Fix styles for repository viewer 2015-08-09 01:41:10 +09:00
Naoki Takezoe
5c46dc0bd3 (refs #865)Fix paginator of the commit list 2015-08-09 01:31:29 +09:00
Naoki Takezoe
db60db674f (refs #865)Update commit list presentation 2015-08-09 01:14:21 +09:00
Naoki Takezoe
687a4f14e1 (refs #865)Fix presentation of file finder and blow view 2015-08-08 21:11:34 +09:00
Naoki Takezoe
bb10365b8b (refs #865)Apply the flat style to box headers 2015-08-08 13:32:42 +09:00
Naoki Takezoe
74ed3bf6a0 Update README.md 2015-08-06 07:17:05 +09:00
Naoki Takezoe
d1d7fdc488 Merge pull request #862 from McFoggy/plugins-info
list installed plugins in the system administration menu
2015-08-06 02:20:07 +09:00
Matthieu Brouillard
67775a4c62 add a comprehensive message when no plugin is detected on the installation 2015-08-05 17:38:14 +02:00
Naoki Takezoe
317b5cb096 Merge pull request #861 from McFoggy/system-admin-extensibility
give an id to system admin menu container to allow plugin extension
2015-08-06 00:16:42 +09:00
Matthieu Brouillard
2929517d7e list installed plugins in the system administration menu 2015-08-05 16:45:00 +02:00
Matthieu Brouillard
51e788396d give an id to system admin menu container to allow plugin extension 2015-08-05 11:38:04 +02:00
Naoki Takezoe
1321653bf6 (refs #848)Additional fix for header width 2015-08-05 02:26:38 +09:00
Naoki Takezoe
3899854854 Merge pull request #848 from garygreen/css-container
Increase container width
2015-08-05 02:21:25 +09:00
Naoki Takezoe
c0ca842ba7 Merge pull request #857 from ssogabe/fixed_build_error
small fixed: env.sh has been already removed.
2015-08-04 00:40:44 +09:00
Seiji Sogabe
24b05d28db env.sh has been already removed. 2015-08-03 19:38:46 +09:00
chocolatle
f0268b105c Fix making bad link from certain references. 2015-08-03 02:14:38 +09:00
Naoki Takezoe
0a46e180a9 Merge pull request #855 from mslinn/master
Updated installation script to GitBucket 3.5
2015-08-03 01:11:42 +09:00
Mike Slinn
e6a215a9c3 Updated to GitBucket 3.5 2015-08-02 08:47:47 -07:00
Naoki Takezoe
8ca7117065 (refs #854)Backup document is moved to Wiki 2015-08-02 12:52:40 +09:00
Naoki Takezoe
ba0a07b835 Merge pull request #854 from McFoggy/backup-gitbucket
add an example of a backup script and some usage instructions
2015-08-02 12:44:22 +09:00
Naoki Takezoe
4a35b65c2c Update release scripts 2015-08-01 03:02:38 +09:00
Naoki Takezoe
836fa47812 GitBucket 3.5.0 release 2015-08-01 01:30:32 +09:00
Matthieu Brouillard
5b658ef6ff add backup script and instructions 2015-07-31 16:39:16 +02:00
Gary Green
e9ff24d9a7 Make header and sidemenu links clickable across full width. 2015-07-29 23:02:29 +01:00
Gary Green
a92051a4c3 Increase container width 2015-07-29 22:52:46 +01:00
Naoki Takezoe
77b3650580 (refs #844)Improve global header menu 2015-07-28 21:53:51 +09:00
Naoki Takezoe
67ee6857ad Remove unnecessary spec 2015-07-28 11:16:02 +09:00
Naoki Takezoe
5ab15c0a14 Merge branch 'beraboris-implement-668' 2015-07-28 11:13:51 +09:00
Naoki Takezoe
96a3f2c301 (refs #810)Some fix about pull request 2015-07-28 11:12:11 +09:00
Naoki Takezoe
85707264c4 Fix position of fork-form 2015-07-28 02:04:49 +09:00
Naoki Takezoe
ed05422ea8 Merge branch 'implement-668' of https://github.com/beraboris/gitbucket into beraboris-implement-668 2015-07-28 01:58:30 +09:00
Naoki Takezoe
8f10c8051e (refs #810)Display Compare (or Pull Request) button for the default branch of the forked repository 2015-07-28 01:57:54 +09:00
Naoki Takezoe
41fff399b5 Redirect to the repository after sign-in by clicking fork button 2015-07-26 03:17:13 +09:00
Naoki Takezoe
9e237647b0 Small fix about icons 2015-07-26 02:59:26 +09:00
Naoki Takezoe
1be53c6746 Merge branch 'sapk-master' 2015-07-23 02:11:17 +09:00
Naoki Takezoe
f2368b03c0 (refs #409)Fix header anchor name in Markdown 2015-07-23 00:53:29 +09:00
Naoki Takezoe
95284c0b36 (refs #409)Fix Markdown style 2015-07-22 02:42:30 +09:00
Naoki Takezoe
f1e21a93fb (refs #409)Fix icons 2015-07-22 02:42:30 +09:00
Naoki Takezoe
689811f659 Add forks icon 2015-07-19 23:23:00 +09:00
Naoki Takezoe
33ccb4e98c Merge branch 'uli-heller-email-sender' 2015-07-19 02:15:03 +09:00
Naoki Takezoe
29f6e98f9c Merge branch 'email-sender' of https://github.com/uli-heller/gitbucket into uli-heller-email-sender 2015-07-19 02:14:36 +09:00
Naoki Takezoe
775c8cc064 Update release operation 2015-07-19 02:04:52 +09:00
Naoki Takezoe
9a974d047c Small fix 2015-07-19 02:01:15 +09:00
Naoki Takezoe
3fd252d2db Remove unnecessary file 2015-07-19 01:47:42 +09:00
Naoki Takezoe
43edb034c8 Merge branch 'linux-ubuntu1404' of https://github.com/uli-heller/gitbucket into uli-heller-linux-ubuntu1404 2015-07-19 01:46:40 +09:00
Naoki Takezoe
e629ca391e Improve env.sh to extract GITBUCKET_VERSION from build.scala 2015-07-19 01:42:42 +09:00
Naoki Takezoe
0db7eba3f2 Merge pull request #836 from shiena/patch/fix_contenttype
Fix duplicated Content-Type
2015-07-18 23:30:27 +09:00
Naoki Takezoe
a472abc88e Merge pull request #829 from muddydixon/fix/group_image_cursor_css
fixed cursor pointer css of group image change
2015-07-18 23:25:04 +09:00
Naoki Takezoe
861c619c19 Merge branch 'master' of https://github.com/sapk/gitbucket into sapk-master 2015-07-18 22:18:02 +09:00
Naoki Takezoe
124f331963 Fix testcase 2015-07-18 22:01:45 +09:00
Mitsuhiro Koga
76fcd44191 Fix duplicated Content-Type 2015-07-16 13:05:40 +09:00
Naoki Takezoe
2124c0b88c Ignore ensime files 2015-07-14 02:49:21 +09:00
Naoki Takezoe
2f5ab8e3b9 (refs #830)Bump pegdown to 1.5.0 and support strike syntax 2015-07-14 02:47:26 +09:00
muddydixon
32c8b6914b fixed cursor pointer css of group image change 2015-07-13 16:54:12 +09:00
Naoki Takezoe
7e9d940f64 Merge branch 'beraboris-better-atom-titles' 2015-07-05 22:17:27 +09:00
Naoki Takezoe
59e826b630 (refs #811)Remove html tags from title of atom feed 2015-07-05 22:15:39 +09:00
Naoki Takezoe
88f3ee4b13 Merge branch 'better-atom-titles' of https://github.com/beraboris/gitbucket into beraboris-better-atom-titles 2015-07-05 21:37:40 +09:00
Naoki Takezoe
b7380a084e Merge pull request #814 from uli-heller/patch-1
Fixes #748 - duplicate c3p0.war
2015-07-05 21:33:36 +09:00
Naoki Takezoe
cb65e790ae (refs #812)Apply GitRepositoryFilter to SSH access too 2015-07-05 15:38:55 +09:00
Naoki Takezoe
573eabee93 (refs #812)Prepend '/' to repository name 2015-07-05 15:08:52 +09:00
Naoki Takezoe
f0d4c6546a (refs #812)SSH support for plug-in served git repository 2015-07-05 14:16:16 +09:00
uli-heller
b0943c87c8 Fixes #748 - duplicate c3p0.war 2015-07-05 06:16:42 +02:00
Boris Bera
4f1208ea98 Atom feed now puts the activity message in title
Originally it used to put the activity type in the message. This gave
no useful information. The user had to open the news item to see what
was going on. Now, the title is the activity's message rendered to html.
Note that this is the same as the content of the news item.

This fixes #481
2015-07-04 15:29:09 -04:00
Naoki Takezoe
02f16639ea Remove twirl-compiler from dependency 2015-07-05 00:50:27 +09:00
Naoki Takezoe
cd5e28c0b8 (refs #812)Add GitRepositoryFilter for authentication 2015-07-05 00:13:34 +09:00
Naoki Takezoe
6f7579f8d9 (refs #812)Add new extension point to serve Git repository by plug-in 2015-07-04 17:42:13 +09:00
Boris Bera
f1d2a71b49 Fixed url generated by pull request/compare button
The `:` character was getting escaped leading to an ugly url. This is
different from the urls generated when editing the branches in a pull
request. In that case the url did not have `:` escaped.
2015-07-03 21:23:53 -04:00
Boris Bera
fd4fe0dc0d Pullreqs in forks now use upstream master as origin
This implements #668.
2015-07-03 20:56:06 -04:00
Naoki Takezoe
3d5e4a4225 (refs #802)Allow '+' in repository name 2015-07-03 21:40:26 +09:00
Naoki Takezoe
10ffb452d0 Fix delete branch confirmation message 2015-07-03 21:19:14 +09:00
Naoki Takezoe
3218bddbdc (refs #808)Add collaborators when fork repository by group 2015-07-03 20:47:37 +09:00
Naoki Takezoe
59f78dcbcb Fix behavior when enableAnchor is false 2015-06-28 03:43:15 +09:00
Naoki Takezoe
0fe062a02f Add option to disable anchor for headline in markdown 2015-06-28 03:20:55 +09:00
Naoki Takezoe
869eaf8cfd Update version to 3.5.0-SNAPSHOT 2015-06-27 23:09:12 +09:00
Naoki Takezoe
5b445c9736 Improve declarative Plugin definition interface 2015-06-27 20:54:19 +09:00
Naoki Takezoe
6f3f3eaa99 Fix Wiki font style of Wiki editing form 2015-06-27 19:41:48 +09:00
Naoki Takezoe
a23ce92676 Add pom.xml for deploying assemble jar 2015-06-27 19:09:43 +09:00
Uli Heller
ad55d5199d Use '{loginName} {fromAddress}' for the from field within the notification emails 2015-06-27 08:45:27 +02:00
Uli Heller
bd05895761 Make the release process work on linux 2015-06-27 07:11:23 +02:00
Uli Heller
c68dd6c891 Another change to make it work on linux - now linux and windows commands are mixed up unfortunately... 2015-06-27 06:56:12 +02:00
uli-heller
daae9ae1e7 Update how_to_run.md
Once "bashism-source" is applied, the build runs smoothly on linux, no need to change anything...
2015-06-27 06:56:12 +02:00
Uli Heller
33dde75ae1 doc change: build.xml -> release/build.xml 2015-06-27 06:56:12 +02:00
Uli Heller
b2e1e1796d Fixed bashism 'source' 2015-06-27 06:16:22 +02:00
Antoine GIRARD
1ff68111b4 Merge remote-tracking branch 'upstream/master' 2015-05-18 15:51:03 +02:00
Antoine GIRARD
dff816324d Rebase + Remove all unused image 2015-03-08 17:15:48 +01:00
Antoine GIRARD
a33e2c6e36 Completely moved to octicon 2015-03-04 23:46:46 +01:00
Antoine GIRARD
75ef82d18a ... 2015-03-04 23:46:46 +01:00
Antoine GIRARD
eb3c522122 Moved to octicon 2015-03-04 23:41:26 +01:00
Antoine GIRARD
6c8bcfc62e Import of octicons 2015-03-04 23:10:55 +01:00
hikaruworld
70c386a934 Disable icone platform is linux && null 2014-08-13 21:28:27 +09:00
hikaruworld
08eb21844a Check the null value of UserAgent 2014-08-13 18:06:56 +09:00
hikaruworld
7b37d6b571 Change to platform userAgent. 2014-08-13 18:06:35 +09:00
hikaruworld
f52bd2bcc0 support Desktop in Clone 2014-08-12 21:45:03 +09:00
187 changed files with 3388 additions and 1457 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
*.class
*.log
.ensime
.ensime_cache
# sbt specific
dist/*

View File

@@ -1,5 +1,6 @@
language: scala
sudo: false
script:
- . env.sh
- sbt test
jdk:
- oraclejdk8

5
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,5 @@
# Guideline for Issues
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/takezoe/gitbucket) before raise an issue.
- When raise a new issue, write subject in **English** at least.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/takezoe/gitbucket_ja).

View File

@@ -14,18 +14,11 @@ The current version of GitBucket provides a basic features below:
- Wiki
- Issues
- Fork / Pull request
- Mail notification
- Email notification
- Activity timeline
- User management (for Administrators)
- Group (like Organization in Github)
- LDAP integration
- Simple user and group management with LDAP integration
- Gravatar support
Following features are not implemented, but we will make them in the future release!
- Network graph
- Statistics
- Watch / Star
- Plug-in system
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
@@ -36,7 +29,7 @@ Installation
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser.
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nignx)
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nginx)
The default administrator account is **root** and password is **root**.
@@ -79,8 +72,42 @@ Run the following commands in `Terminal` to
- start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist`
- stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist`
Plug-ins
--------
GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. Some plug-ins are available now:
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
- [gitbucket-announce-plugin](https://github.com/gitbucket-plugins/gitbucket-announce-plugin)
- [gitbucket-h2-backup-plugin](https://github.com/gitbucket-plugins/gitbucket-h2-backup-plugin)
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
Support
--------
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/takezoe/gitbucket) before raise an issue.
- When raise a new issue, write subject in **English** at least.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/takezoe/gitbucket_ja).
Release Notes
--------
### 3.7 - 3 Oct 2015
- Markdown processor has been switched to [markedj](https://github.com/gitbucket/markedj) from pegdown
- Clone in desktop button
### 3.6 - 30 Aug 2015
- User interface Improvements: Especially, commit list, issues and pull request have been updated largely.
- Installed plugins list has been available at the system administration console.
- Pages and repository list in the sidebar have been limited and more pages and repositories link is available.
- More reference link notation in Markdown has been supported.
### 3.5 - 1 Aug 2015
- Octicons has been applied
- Global header has been enhanced. Now it's further similar to GitHub.
- Default compare / pull request target has been changed to the parent repository
- A lot of updates for [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
### 3.4 - 27 Jun 2015
- Declarative style plug-in definition
- New extension point to add markup render

View File

@@ -44,7 +44,7 @@ GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
# GitBucket version to fetch when installing
GITBUCKET_VERSION=2.1
GITBUCKET_VERSION=3.5
#
# End of configuration section. Ignore this part

View File

@@ -2,7 +2,7 @@ Automatic Schema Updating
========
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
To release a new version of GitBucket, add the version definition to the [servlet.AutoUpdate](https://github.com/takezoe/gitbucket/blob/master/src/main/scala/servlet/AutoUpdateListener.scala) at first.
To release a new version of GitBucket, add the version definition to the [gitbucket.core.servlet.AutoUpdate](https://github.com/takezoe/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala) at first.
```scala
object AutoUpdate {

View File

@@ -16,6 +16,8 @@ for Developers
--------
If you want to modify source code and confirm it, you can run GitBucket in auto reloading mode as following:
Windows:
```
C:\gitbucket> sbt
...
@@ -24,15 +26,38 @@ C:\gitbucket> sbt
> ~ ;copy-resources;aux-compile
```
Linux:
```
~/gitbucket$ ./sbt.sh
...
> container:start
...
> ~ ;copy-resources;aux-compile
```
Build war file
--------
To build war file, run the following command:
Windows:
```
C:\gitbucket> sbt package
```
Linux:
```
~/gitbucket$ ./sbt.sh package
```
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
To build executable war file, run Ant at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact. Please note the current build.xml works on Windows only. Replace `sbt.bat` with `sbt.sh` in build.xml if you want to run it on Linux.
To build executable war file, run
* Windows: Not available
* Linux: `./release/make-release-war.sh`
at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact.

View File

@@ -379,21 +379,21 @@
<path d="M588.909,926.673 L560.094,910.545 L564.713,935.73 L588.909,926.673 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
</g>
<g id="rect3075-11">
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill="#FFFFFF"/>
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
<path d="M1389.845,733.625 C1389.679,748.281 1389.512,762.937 1389.345,777.593 C1391.543,777.615 1393.741,777.635 1395.939,777.656 C1394.387,779.219 1392.835,780.781 1391.283,782.344 C1411.991,803.052 1432.7,823.76 1453.408,844.469 C1468.814,829.062 1484.221,813.656 1499.627,798.25 C1478.918,777.531 1458.21,756.812 1437.502,736.094 C1435.773,737.833 1434.043,739.573 1432.314,741.312 C1432.335,738.917 1432.356,736.521 1432.377,734.125 C1418.2,733.958 1404.023,733.792 1389.846,733.625 z" fill="#FFFFFF"/>
<path d="M1389.845,733.625 C1389.679,748.281 1389.512,762.937 1389.345,777.593 C1391.543,777.615 1393.741,777.635 1395.939,777.656 C1394.387,779.219 1392.835,780.781 1391.283,782.344 C1411.991,803.052 1432.7,823.76 1453.408,844.469 C1468.814,829.062 1484.221,813.656 1499.627,798.25 C1478.918,777.531 1458.21,756.812 1437.502,736.094 C1435.773,737.833 1434.043,739.573 1432.314,741.312 C1432.335,738.917 1432.356,736.521 1432.377,734.125 C1418.2,733.958 1404.023,733.792 1389.846,733.625 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
</g>
<path d="M606.483,964.91 L606.483,951.243 L672.089,951.243 L672.089,964.91 z" fill="#B3B3B3" id="rect2995-0-2-8-6"/>
<g id="rect3075-11-7">
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill="#FFFFFF"/>
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
<path d="M1396.666,740.606 C1396.539,751.76 1396.412,762.914 1396.286,774.068 C1397.958,774.084 1399.631,774.099 1401.304,774.115 C1400.122,775.304 1398.941,776.494 1397.76,777.683 C1413.52,793.442 1429.28,809.202 1445.039,824.962 C1456.764,813.237 1468.489,801.513 1480.213,789.788 C1464.454,774.02 1448.694,758.253 1432.934,742.485 C1431.618,743.809 1430.302,745.133 1428.986,746.457 C1429.002,744.633 1429.017,742.81 1429.034,740.987 C1418.244,740.86 1407.455,740.733 1396.666,740.606 z" fill="#FFFFFF"/>
<path d="M1396.666,740.606 C1396.539,751.76 1396.412,762.914 1396.286,774.068 C1397.958,774.084 1399.631,774.099 1401.304,774.115 C1400.122,775.304 1398.941,776.494 1397.76,777.683 C1413.52,793.442 1429.28,809.202 1445.039,824.962 C1456.764,813.237 1468.489,801.513 1480.213,789.788 C1464.454,774.02 1448.694,758.253 1432.934,742.485 C1431.618,743.809 1430.302,745.133 1428.986,746.457 C1429.002,744.633 1429.017,742.81 1429.034,740.987 C1418.244,740.86 1407.455,740.733 1396.666,740.606 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
</g>
<g id="path3100-2">
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill="#FFFFFF"/>
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
<path d="M1424.031,752.219 C1428.538,756.726 1428.538,764.032 1424.031,768.539 C1419.525,773.045 1412.218,773.045 1407.712,768.539 C1403.205,764.032 1403.205,756.726 1407.712,752.219 C1412.218,747.713 1419.525,747.713 1424.031,752.219 z" fill="#FFFFFF"/>
<path d="M1424.031,752.219 C1428.538,756.726 1428.538,764.032 1424.031,768.539 C1419.525,773.045 1412.218,773.045 1407.712,768.539 C1403.205,764.032 1403.205,756.726 1407.712,752.219 C1412.218,747.713 1419.525,747.713 1424.031,752.219 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
</g>
<g id="rect4114">
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill="#FFFFFF"/>
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
<path d="M1424.128,790.638 L1445.171,769.595 L1474.295,798.719 L1453.252,819.762 z" fill="#FFFFFF"/>
<path d="M1424.128,790.638 L1445.171,769.595 L1474.295,798.719 L1453.252,819.762 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
</g>
<g id="path2991-7-6">
<path d="M969.889,84.636 C969.889,122.652 939.072,153.469 901.056,153.469 C863.04,153.469 832.223,122.652 832.223,84.636 C832.223,46.62 863.04,15.803 901.056,15.803 C939.072,15.803 969.889,46.62 969.889,84.636 z" fill="#A0A0A0"/>
@@ -750,5 +750,45 @@
</g>
<path d="M1396.792,592.168 C1426.908,592.168 1450.613,610.989 1450.613,639.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
<path d="M1397.792,545.653 C1453.613,544.493 1499.627,588.735 1499.627,636.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
<path d="M871.125,1039.025 C871.125,1039.025 873.794,1016.889 908.043,1011.524 C919.748,1009.691 945.861,1005.107 945.861,978.522" fill-opacity="0" stroke="#A0A0A0" stroke-width="17.059" id="path3207"/>
<g id="rect3818-4-8-4">
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill="#A0A0A0"/>
<g>
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill="#A0A0A0"/>
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="15"/>
</g>
</g>
<g id="path3795-4-8-7-8">
<path d="M888.074,1051.656 C888.074,1060.906 880.5,1068.405 871.159,1068.405 C861.817,1068.405 854.243,1060.906 854.243,1051.656 C854.243,1042.406 861.817,1034.908 871.159,1034.908 C880.5,1034.908 888.074,1042.406 888.074,1051.656 z" fill="#FFFFFF"/>
<path d="M888.074,1051.656 C888.074,1060.906 880.5,1068.405 871.159,1068.405 C861.817,1068.405 854.243,1060.906 854.243,1051.656 C854.243,1042.406 861.817,1034.908 871.159,1034.908 C880.5,1034.908 888.074,1042.406 888.074,1051.656 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8">
<path d="M886.883,935.155 C886.883,944.404 879.31,951.903 869.968,951.903 C860.626,951.903 853.054,944.404 853.054,935.155 C853.054,925.904 860.626,918.405 869.968,918.405 C879.31,918.405 886.883,925.904 886.883,935.155 z" fill="#FFFFFF"/>
<path d="M886.883,935.155 C886.883,944.404 879.31,951.903 869.968,951.903 C860.626,951.903 853.054,944.404 853.054,935.155 C853.054,925.904 860.626,918.405 869.968,918.405 C879.31,918.405 886.883,925.904 886.883,935.155 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8-2">
<path d="M965.046,971.602 C965.046,980.852 957.472,988.351 948.13,988.351 C938.789,988.351 931.215,980.852 931.215,971.602 C931.215,962.352 938.789,954.854 948.13,954.854 C957.472,954.854 965.046,962.352 965.046,971.602 z" fill="#FFFFFF"/>
<path d="M965.046,971.602 C965.046,980.852 957.472,988.351 948.13,988.351 C938.789,988.351 931.215,980.852 931.215,971.602 C931.215,962.352 938.789,954.854 948.13,954.854 C957.472,954.854 965.046,962.352 965.046,971.602 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
</g>
<path d="M1114.353,1042.412 C1114.353,1042.412 1117.022,1020.275 1151.271,1014.91 C1162.976,1013.077 1189.089,1008.493 1189.089,981.909" fill-opacity="0" stroke="#000000" stroke-width="17.059" id="path3207"/>
<g id="rect3818-4-8-4">
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill="#A0A0A0"/>
<g>
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill="#A0A0A0"/>
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill-opacity="0" stroke="#000000" stroke-width="15"/>
</g>
</g>
<g id="path3795-4-8-7-8">
<path d="M1131.302,1055.043 C1131.302,1064.293 1123.728,1071.791 1114.386,1071.791 C1105.045,1071.791 1097.471,1064.293 1097.471,1055.043 C1097.471,1045.792 1105.045,1038.294 1114.386,1038.294 C1123.728,1038.294 1131.302,1045.792 1131.302,1055.043 z" fill="#FFFFFF"/>
<path d="M1131.302,1055.043 C1131.302,1064.293 1123.728,1071.791 1114.386,1071.791 C1105.045,1071.791 1097.471,1064.293 1097.471,1055.043 C1097.471,1045.792 1105.045,1038.294 1114.386,1038.294 C1123.728,1038.294 1131.302,1045.792 1131.302,1055.043 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8">
<path d="M1130.111,938.542 C1130.111,947.791 1122.537,955.29 1113.196,955.29 C1103.854,955.29 1096.282,947.791 1096.282,938.542 C1096.282,929.291 1103.854,921.792 1113.196,921.792 C1122.537,921.792 1130.111,929.291 1130.111,938.542 z" fill="#FFFFFF"/>
<path d="M1130.111,938.542 C1130.111,947.791 1122.537,955.29 1113.196,955.29 C1103.854,955.29 1096.282,947.791 1096.282,938.542 C1096.282,929.291 1103.854,921.792 1113.196,921.792 C1122.537,921.792 1130.111,929.291 1130.111,938.542 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8-2">
<path d="M1208.274,974.989 C1208.274,984.239 1200.7,991.738 1191.358,991.738 C1182.016,991.738 1174.443,984.239 1174.443,974.989 C1174.443,965.739 1182.016,958.241 1191.358,958.241 C1200.7,958.241 1208.274,965.739 1208.274,974.989 z" fill="#FFFFFF"/>
<path d="M1208.274,974.989 C1208.274,984.239 1200.7,991.738 1191.358,991.738 C1182.016,991.738 1174.443,984.239 1174.443,974.989 C1174.443,965.739 1182.016,958.241 1191.358,958.241 C1200.7,958.241 1208.274,965.739 1208.274,974.989 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -6,6 +6,17 @@ Update version number
Note to update version number in files below:
### project/build.scala
```scala
object MyBuild extends Build {
val Organization = "gitbucket"
val Name = "gitbucket"
val Version = "3.3.0" // <---- update version!!
val ScalaVersion = "2.11.6"
val ScalatraVersion = "2.3.1"
```
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
```scala
@@ -19,13 +30,6 @@ object AutoUpdate {
new Version(3, 2),
```
### env.sh
```bash
#!/bin/sh
export GITBUCKET_VERSION=3.3.0 # <---- update here!!
```
Generate release files
--------

2
env.sh
View File

@@ -1,2 +0,0 @@
#!/bin/sh
export GITBUCKET_VERSION=3.4.0

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -10,7 +10,7 @@ import sbtassembly.AssemblyKeys._
object MyBuild extends Build {
val Organization = "gitbucket"
val Name = "gitbucket"
val Version = System.getenv("GITBUCKET_VERSION")
val Version = "3.7.0"
val ScalaVersion = "2.11.6"
val ScalatraVersion = "2.3.1"
@@ -38,7 +38,8 @@ object MyBuild extends Build {
scalaVersion := ScalaVersion,
resolvers ++= Seq(
Classpaths.typesafeReleases,
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
),
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
libraryDependencies ++= Seq(
@@ -50,7 +51,7 @@ object MyBuild extends Build {
"org.json4s" %% "json4s-jackson" % "3.2.11",
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
"commons-io" % "commons-io" % "2.4",
"org.pegdown" % "pegdown" % "1.4.1", // 1.4.2 has incompatible APi changes
"io.github.gitbucket" % "markedj" % "1.0.3",
"org.apache.commons" % "commons-compress" % "1.9",
"org.apache.commons" % "commons-email" % "1.3.3",
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
@@ -64,9 +65,8 @@ object MyBuild extends Build {
"junit" % "junit" % "4.12" % "test",
"com.mchange" % "c3p0" % "0.9.5",
"com.typesafe" % "config" % "1.2.1",
"com.typesafe.play" %% "twirl-compiler" % "1.0.4",
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x"
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" exclude("c3p0","c3p0")
),
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
EclipseKeys.withSource := true,

View File

@@ -1,5 +1,5 @@
#!/bin/sh
source ../env.sh
. ./env.sh
cd ../
./sbt.sh clean assembly

3
release/env.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
export GITBUCKET_VERSION=`cat ../project/build.scala | grep 'val Version' | cut -d \" -f 2`
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"

View File

@@ -1,3 +1,15 @@
#!/bin/sh
source ../env.sh
ant -f build.xml all
D="$(dirname "$0")"
D="$(cd "${D}"; pwd)"
DD="$(dirname "${D}")"
(
for f in "${D}/env.sh" "${D}/build.xml"; do
if [ ! -s "${f}" ]; then
echo >&2 "$0: Unable to access file '${f}'"
exit 1
fi
done
. "${D}/env.sh"
cd "${DD}"
ant -f "${D}/build.xml" all
)

17
release/pom.xml Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jp.sf.amateras</groupId>
<artifactId>gitbucket-assembly</artifactId>
<version>0.0.1</version>
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>1.0-beta-6</version>
</extension>
</extensions>
</build>
</project>

View File

@@ -1,2 +1,2 @@
set SCRIPT_DIR=%~dp0
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.8.jar" %*
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.8.jar" %*

3
sbt.sh
View File

@@ -1,3 +1,2 @@
#!/bin/sh
source ./env.sh
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.8.jar "$@"
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.8.jar "$@"

View File

@@ -32,6 +32,7 @@ class ScalatraBootstrap extends LifeCycle {
context.mount(new DashboardController, "/*")
context.mount(new UserManagementController, "/*")
context.mount(new SystemSettingsController, "/*")
context.mount(new PluginsController, "/*")
context.mount(new AccountController, "/*")
context.mount(new RepositoryViewerController, "/*")
context.mount(new WikiController, "/*")

View File

@@ -14,16 +14,16 @@ case class ApiComment(
user: ApiUser,
body: String,
created_at: Date,
updated_at: Date)(repositoryName: RepositoryName, issueId: Int){
val html_url = ApiPath(s"/${repositoryName.fullName}/issues/${issueId}#comment-${id}")
updated_at: Date)(repositoryName: RepositoryName, issueId: Int, isPullRequest: Boolean){
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${issueId}#comment-${id}")
}
object ApiComment{
def apply(comment: IssueComment, repositoryName: RepositoryName, issueId: Int, user: ApiUser): ApiComment =
def apply(comment: IssueComment, repositoryName: RepositoryName, issueId: Int, user: ApiUser, isPullRequest: Boolean): ApiComment =
ApiComment(
id = comment.commentId,
user = user,
body = comment.content,
created_at = comment.registeredDate,
updated_at = comment.updatedDate)(repositoryName, issueId)
updated_at = comment.updatedDate)(repositoryName, issueId, isPullRequest)
}

View File

@@ -20,7 +20,7 @@ case class ApiCommit(
removed: List[String],
modified: List[String],
author: ApiPersonIdent,
committer: ApiPersonIdent)(repositoryName:RepositoryName){
committer: ApiPersonIdent)(repositoryName:RepositoryName) extends FieldSerializable{
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
}

View File

@@ -17,9 +17,9 @@ case class ApiIssue(
state: String,
created_at: Date,
updated_at: Date,
body: String)(repositoryName: RepositoryName){
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/issues/${number}")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
}
object ApiIssue{
@@ -31,5 +31,5 @@ object ApiIssue{
state = if(issue.closed){ "closed" }else{ "open" },
body = issue.content.getOrElse(""),
created_at = issue.registeredDate,
updated_at = issue.updatedDate)(repositoryName)
updated_at = issue.updatedDate)(repositoryName, issue.isPullRequest)
}

View File

@@ -0,0 +1,39 @@
package gitbucket.core.api
import gitbucket.core.util.JGitUtil
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.RepositoryName
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.api.Git
import java.util.Date
/**
* https://developer.github.com/v3/activity/events/types/#pushevent
*/
case class ApiPushCommit(
id: String,
message: String,
timestamp: Date,
added: List[String],
removed: List[String],
modified: List[String],
author: ApiPersonIdent,
committer: ApiPersonIdent)(repositoryName:RepositoryName) extends FieldSerializable {
val url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
}
object ApiPushCommit{
def apply(commit: ApiCommit, repositoryName: RepositoryName): ApiPushCommit = ApiPushCommit(
id = commit.id,
message = commit.message,
timestamp = commit.timestamp,
added = commit.added,
removed = commit.removed,
modified = commit.modified,
author = commit.author,
committer = commit.committer)(repositoryName)
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiPushCommit =
ApiPushCommit(ApiCommit(git, repositoryName, commit), repositoryName)
}

View File

@@ -15,7 +15,7 @@ case class ApiRepository(
default_branch: String,
owner: ApiUser) {
val forks_count = forks
val watchers_coun = watchers
val watchers_count = watchers
val url = ApiPath(s"/api/v3/repos/${full_name}")
val http_url = ApiPath(s"/git/${full_name}.git")
val clone_url = ApiPath(s"/git/${full_name}.git")

View File

@@ -0,0 +1,4 @@
package gitbucket.core.api
/** export fields for json */
trait FieldSerializable

View File

@@ -22,7 +22,7 @@ object JsonFormat {
)
) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() +
FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() +
FieldSerializer[ApiCommitStatus]() + FieldSerializer[ApiCommit]() + FieldSerializer[ApiCombinedCommitStatus]() +
FieldSerializer[ApiCommitStatus]() + FieldSerializer[FieldSerializable]() + FieldSerializer[ApiCombinedCommitStatus]() +
FieldSerializer[ApiPullRequest.Commit]() + FieldSerializer[ApiIssue]() + FieldSerializer[ApiComment]()

View File

@@ -91,7 +91,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val newRepositoryForm = mapping(
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, uniqueRepository))),
"name" -> trim(label("Repository name", text(required, maxlength(40), repository, uniqueRepository))),
"description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())),
"createReadme" -> trim(label("Create README" , boolean()))
@@ -467,6 +467,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
parentUserName = Some(repository.owner)
)
// Add collaborators for group repository
val ownerAccount = getAccountByUserName(accountName).get
if(ownerAccount.isGroupAccount){
getGroupMembers(accountName).foreach { member =>
addCollaborator(accountName, repository.name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(accountName, repository.name)

View File

@@ -181,6 +181,13 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
val baseUrl = settings.baseUrl(request)
val host = new java.net.URL(baseUrl).getHost
val platform = request.getHeader("User-Agent") match {
case null => null
case agent if agent.contains("Mac") => "mac"
case agent if agent.contains("Linux") => "linux"
case agent if agent.contains("Win") => "windows"
case _ => null
}
/**
* Get object from cache.

View File

@@ -86,7 +86,7 @@ trait IssuesControllerBase extends ControllerBase {
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield {
JsonFormat(comments.map{ case (issueComment, user) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user)) })
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}).getOrElse(NotFound)
})
@@ -190,7 +190,7 @@ trait IssuesControllerBase extends ControllerBase {
(issue, id) <- handleComment(issueId, Some(body), repository)()
issueComment <- getComment(repository.owner, repository.name, id.toString())
} yield {
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get)))
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
}) getOrElse NotFound
})
@@ -233,7 +233,7 @@ trait IssuesControllerBase extends ControllerBase {
org.json4s.jackson.Serialization.write(
Map("title" -> x.title,
"content" -> Markdown.toHtml(x.content getOrElse "No description given.",
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
repository, false, true, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
))
}
} else Unauthorized
@@ -257,6 +257,12 @@ trait IssuesControllerBase extends ControllerBase {
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/issues/new/label")(collaboratorsOnly { repository =>
val labelNames = params("labelNames").split(",")
val labels = getLabels(repository.owner, repository.name).filter(x => labelNames.contains(x.labelName))
html.labellist(labels)
})
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
defining(params("id").toInt){ issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
@@ -459,7 +465,11 @@ trait IssuesControllerBase extends ControllerBase {
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
(getCollaborators(owner, repoName) :+ owner).sorted,
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted
} else {
getCollaborators(owner, repoName)
},
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), false, owner -> repoName),

View File

@@ -0,0 +1,11 @@
package gitbucket.core.controller
import gitbucket.core.admin.plugins.html
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.util.AdminAuthenticator
class PluginsController extends ControllerBase with AdminAuthenticator {
get("/admin/plugins")(adminOnly {
html.plugins(PluginRegistry().getPlugins())
})
}

View File

@@ -46,7 +46,10 @@ trait PullRequestsControllerBase extends ControllerBase {
"requestRepositoryName" -> trim(text(required, maxlength(100))),
"requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40)))
"commitIdTo" -> trim(text(required, maxlength(40))),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(PullRequestForm.apply)
val mergeForm = mapping(
@@ -62,7 +65,11 @@ trait PullRequestsControllerBase extends ControllerBase {
requestRepositoryName: String,
requestBranch: String,
commitIdFrom: String,
commitIdTo: String)
commitIdTo: String,
assignedUserName: Option[String],
milestoneId: Option[Int],
labelNames: Option[String]
)
case class MergeForm(message: String)
@@ -176,7 +183,7 @@ trait PullRequestsControllerBase extends ControllerBase {
pullreq,
statuses,
repository,
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
}
} getOrElse NotFound
})
@@ -232,6 +239,9 @@ trait PullRequestsControllerBase extends ControllerBase {
}
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
}
updatePullRequests(owner, name, pullreq.branch)
// call web hook
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
@@ -307,32 +317,44 @@ trait PullRequestsControllerBase extends ControllerBase {
originRepository.owner, originRepository.name, originId,
forkedRepository.owner, forkedRepository.name, forkedId)
(oldGit.getRepository.resolve(rootId), newGit.getRepository.resolve(forkedId))
(Option(oldGit.getRepository.resolve(rootId)), Option(newGit.getRepository.resolve(forkedId)))
} else {
// Commit id
(oldGit.getRepository.resolve(originId), newGit.getRepository.resolve(forkedId))
(Option(oldGit.getRepository.resolve(originId)), Option(newGit.getRepository.resolve(forkedId)))
}
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner, originRepository.name, oldId.getName,
forkedRepository.owner, forkedRepository.name, newId.getName)
(oldId, newId) match {
case (Some(oldId), Some(newId)) => {
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner, originRepository.name, oldId.getName,
forkedRepository.owner, forkedRepository.name, newId.getName)
html.compare(
commits,
diffs,
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
},
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
originId,
forkedId,
oldId.getName,
newId.getName,
forkedRepository,
originRepository,
forkedRepository,
hasWritePermission(forkedRepository.owner, forkedRepository.name, context.loginAccount))
html.compare(
commits,
diffs,
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
},
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
originId,
forkedId,
oldId.getName,
newId.getName,
forkedRepository,
originRepository,
forkedRepository,
hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount),
(getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted,
getMilestones(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name)
)
}
case (oldId, newId) =>
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}")
}
}
}) getOrElse NotFound
})
@@ -368,46 +390,61 @@ trait PullRequestsControllerBase extends ControllerBase {
})
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
val loginUserName = context.loginAccount.get.userName
defining(repository.owner, repository.name){ case (owner, name) =>
val writable = hasWritePermission(owner, name, context.loginAccount)
val loginUserName = context.loginAccount.get.userName
val issueId = createIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,
title = form.title,
content = form.content,
assignedUserName = None,
milestoneId = None,
isPullRequest = true)
val issueId = createIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,
title = form.title,
content = form.content,
assignedUserName = if(writable) form.assignedUserName else None,
milestoneId = if(writable) form.milestoneId else None,
isPullRequest = true)
createPullRequest(
originUserName = repository.owner,
originRepositoryName = repository.name,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo)
createPullRequest(
originUserName = repository.owner,
originRepositoryName = repository.name,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo)
// fetch requested branch
fetchAsPullRequest(repository.owner, repository.name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
// record activity
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
// call web hook
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
// notifications
getIssue(repository.owner, repository.name, issueId.toString) foreach { issue =>
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
// insert labels
if(writable){
form.labelNames.map { value =>
val labels = getLabels(owner, name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
}
}
}
}
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
// fetch requested branch
fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
// record activity
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
// call web hook
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
// notifications
getIssue(owner, name, issueId.toString) foreach { issue =>
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
}
}
redirect(s"/${owner}/${name}/pull/${issueId}")
}
})
/**
@@ -459,7 +496,11 @@ trait PullRequestsControllerBase extends ControllerBase {
"pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page,
(getCollaborators(owner, repoName) :+ owner).sorted,
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted
} else {
getCollaborators(owner, repoName)
},
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), true, owner -> repoName),

View File

@@ -22,6 +22,7 @@ import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.errors.MissingObjectException
import org.eclipse.jgit.lib._
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._
@@ -249,7 +250,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
)
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
if(form.path.length == 0) urlEncode(form.newFileName) else s"${form.path}/${urlEncode(form.newFileName)}"
}")
})
@@ -270,7 +271,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
)
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
if(form.path.length == 0) urlEncode(form.newFileName) else s"${form.path}/${urlEncode(form.newFileName)}"
}")
})
@@ -344,16 +345,21 @@ trait RepositoryViewerControllerBase extends ControllerBase {
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
val id = params("id")
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName),
getCommitComments(repository.owner, repository.name, id, false),
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
try {
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { revCommit =>
JGitUtil.getDiffs(git, id) match {
case (diffs, oldCommitId) =>
html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName),
getCommitComments(repository.owner, repository.name, id, false),
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
}
}
}
} catch {
case e:MissingObjectException => NotFound
}
})
@@ -436,10 +442,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Displays branches.
*/
get("/:owner/:repository/branches")(referrersOnly { repository =>
val branches = JGitUtil.getBranches(repository.owner, repository.name, repository.repository.defaultBranch)
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
.reverse
val branches = JGitUtil.getBranches(
owner = repository.owner,
name = repository.name,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
.reverse
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
})
@@ -511,10 +523,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
/**
* Displays the file find of branch.
*/
get("/:owner/:repository/find/:ref")(referrersOnly { repository =>
get("/:owner/:repository/find/*")(referrersOnly { repository =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getTreeId(git, params("ref")).map{ treeId =>
html.find(params("ref"),
val ref = multiParams("splat").head
JGitUtil.getTreeId(git, ref).map{ treeId =>
html.find(ref,
treeId,
repository,
context.loginAccount match {

View File

@@ -24,7 +24,8 @@ trait SystemSettingsControllerBase extends ControllerBase {
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
"ssh" -> trim(label("SSH access", boolean())),
"sshPort" -> trim(label("SSH port", optional(number()))),
"smtp" -> optionalIfNotChecked("notification", mapping(
"useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked("useSMTP", mapping(
"host" -> trim(label("SMTP Host", text(required))),
"port" -> trim(label("SMTP Port", optional(number()))),
"user" -> trim(label("SMTP User", optional(text()))),

View File

@@ -0,0 +1,42 @@
package gitbucket.core.plugin
import gitbucket.core.model.Session
import gitbucket.core.service.SystemSettingsService.SystemSettings
/**
* Define the Git repository routing.
*
* @param urlPattern the regular expression which matches the repository path (e.g. "gist/(.+?)/(.+?)\\.git")
* @param localPath the string to assemble local file path of repository (e.g. "gist/$1/$2")
* @param filter the filter for request to the Git repository which is defined by this routing
*/
case class GitRepositoryRouting(urlPattern: String, localPath: String, filter: GitRepositoryFilter){
def this(urlPattern: String, localPath: String) = {
this(urlPattern, localPath, new GitRepositoryFilter(){
def filter(repositoryName: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)
(implicit session: Session): Boolean = true
})
}
}
/**
* Filters request to plug-in served repository. This is used to provide authentication mainly.
*/
trait GitRepositoryFilter {
/**
* Filters request to Git repository. If this method returns true then request is accepted.
*
* @param path the repository path which starts with '/'
* @param userName the authenticated user name or None
* @param settings the system settings
* @param isUpdating true if update request, otherwise false
* @param session the database session
* @return true if allow accessing to repository, otherwise false.
*/
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)
(implicit session: Session): Boolean
}

View File

@@ -22,38 +22,71 @@ trait Plugin {
*/
val images: Seq[(String, Array[Byte])] = Nil
/**
* Override to declare this plug-in provides images.
*/
def images(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Array[Byte])] = Nil
/**
* Override to declare this plug-in provides controllers.
*/
val controllers: Seq[(String, ControllerBase)] = Nil
/**
* Override to declare this plug-in provides controllers.
*/
def controllers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, ControllerBase)] = Nil
/**
* Override to declare this plug-in provides JavaScript.
*/
val javaScripts: Seq[(String, String)] = Nil
/**
* Override to declare this plug-in provides JavaScript.
*/
def javaScripts(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil
/**
* Override to declare this plug-in provides renderers.
*/
val renderers: Seq[(String, Renderer)] = Nil
/**
* Override to declare this plug-in provides renderers.
*/
def renderers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Renderer)] = Nil
/**
* Override to add git repository routings.
*/
val repositoryRoutings: Seq[GitRepositoryRouting] = Nil
/**
* Override to add git repository routings.
*/
def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil
/**
* This method is invoked in initialization of plugin system.
* Register plugin functionality to PluginRegistry.
*/
def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {
images.foreach { case (id, in) =>
(images ++ images(registry, context, settings)).foreach { case (id, in) =>
registry.addImage(id, in)
}
controllers.foreach { case (path, controller) =>
(controllers ++ controllers(registry, context, settings)).foreach { case (path, controller) =>
registry.addController(path, controller)
}
javaScripts.foreach { case (path, script) =>
(javaScripts ++ javaScripts(registry, context, settings)).foreach { case (path, script) =>
registry.addJavaScript(path, script)
}
renderers.foreach { case (extension, renderer) =>
(renderers ++ renderers(registry, context, settings)).foreach { case (extension, renderer) =>
registry.addRenderer(extension, renderer)
}
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
registry.addRepositoryRouting(routing)
}
}
/**

View File

@@ -28,6 +28,7 @@ class PluginRegistry {
renderers ++= Seq(
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
)
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
def addPlugin(pluginInfo: PluginInfo): Unit = {
plugins += pluginInfo
@@ -61,7 +62,7 @@ class PluginRegistry {
addController(path, controller)
}
def getControllers(): List[(ControllerBase, String)] = controllers.toList
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
def addJavaScript(path: String, script: String): Unit = {
javaScripts += ((path, script))
@@ -81,6 +82,22 @@ class PluginRegistry {
def renderableExtensions: Seq[String] = renderers.keys.toSeq
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = {
repositoryRoutings += routing
}
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = {
repositoryRoutings.toSeq
}
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
PluginRegistry().getRepositoryRoutings().find {
case GitRepositoryRouting(urlPath, _, _) => {
repositoryPath.matches("/" + urlPath + "(/.*)?")
}
}
}
private case class GlobalAction(
method: String,
path: String,

View File

@@ -20,7 +20,7 @@ trait Renderer {
object MarkdownRenderer extends Renderer {
override def render(request: RenderRequest): Html = {
import request._
Html(Markdown.toHtml(fileContent, repository, enableWikiLink, enableRefsLink)(context))
Html(Markdown.toHtml(fileContent, repository, enableWikiLink, enableRefsLink, enableAnchor)(context))
}
}
@@ -41,4 +41,5 @@ case class RenderRequest(filePath: List[String],
repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean,
enableRefsLink: Boolean,
enableAnchor: Boolean,
context: Context)

View File

@@ -22,11 +22,13 @@ trait IssuesService {
def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) =
IssueComments filter (_.byIssue(owner, repository, issueId)) list
/** @return IssueComment and commentedUser */
def getCommentsForApi(owner: String, repository: String, issueId: Int)(implicit s: Session): List[(IssueComment, Account)] =
/** @return IssueComment and commentedUser and Issue */
def getCommentsForApi(owner: String, repository: String, issueId: Int)(implicit s: Session): List[(IssueComment, Account, Issue)] =
IssueComments.filter(_.byIssue(owner, repository, issueId))
.filter(_.action inSetBind Set("comment" , "close_comment", "reopen_comment"))
.innerJoin(Accounts).on( (t1, t2) => t1.commentedUserName === t2.userName )
.innerJoin(Issues).on{ case ((t1, t2), t3) => t3.byIssue(t1.userName, t1.repositoryName, t1.issueId) }
.map{ case ((t1, t2), t3) => (t1, t2, t3) }
.list
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
@@ -474,9 +476,11 @@ object IssuesService {
* Restores IssueSearchCondition instance from filter query.
*/
def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = {
val conditions = filter.split("[  \t]+").map { x =>
val dim = x.split(":")
dim(0) -> dim(1)
val conditions = filter.split("[  \t]+").flatMap { x =>
x.split(":") match {
case Array(key, value) => Some((key, value))
case _ => None
}
}.groupBy(_._1).map { case (key, values) =>
key -> values.map(_._2).toSeq
}

View File

@@ -383,6 +383,12 @@ object RepositoryService {
def sshUrl(port: Int, userName: String) = s"ssh://${userName}@${host}:${port}/${owner}/${name}.git"
def sshOpenRepoUrl(platform: String, port: Int, userName: String) = openRepoUrl(platform, sshUrl(port, userName))
def httpOpenRepoUrl(platform: String) = openRepoUrl(platform, httpUrl)
def openRepoUrl(platform: String, openUrl: String) = s"github-${platform}://openRepo/${openUrl}"
/**
* Creates instance with issue count and pull request count.
*/

View File

@@ -22,7 +22,8 @@ trait SystemSettingsService {
settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString))
props.setProperty(Ssh, settings.ssh.toString)
settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
if(settings.notification) {
props.setProperty(UseSMTP, settings.useSMTP.toString)
if(settings.useSMTP) {
settings.smtp.foreach { smtp =>
props.setProperty(SmtpHost, smtp.host)
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
@@ -75,7 +76,8 @@ trait SystemSettingsService {
getOptionValue[Int](props, ActivityLogLimit, None),
getValue(props, Ssh, false),
getOptionValue(props, SshPort, Some(DefaultSshPort)),
if(getValue(props, Notification, false)){
getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP
if(getValue(props, UseSMTP, getValue(props, Notification, false))){
Some(Smtp(
getValue(props, SmtpHost, ""),
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
@@ -125,6 +127,7 @@ object SystemSettingsService {
activityLogLimit: Option[Int],
ssh: Boolean,
sshPort: Option[Int],
useSMTP: Boolean,
smtp: Option[Smtp],
ldapAuthentication: Boolean,
ldap: Option[Ldap]){
@@ -172,6 +175,7 @@ object SystemSettingsService {
private val ActivityLogLimit = "activity_log_limit"
private val Ssh = "ssh"
private val SshPort = "ssh.port"
private val UseSMTP = "useSMTP"
private val SmtpHost = "smtp.host"
private val SmtpPort = "smtp.port"
private val SmtpUser = "smtp.user"

View File

@@ -192,7 +192,7 @@ object WebHookService {
case class WebHookPushPayload(
pusher: ApiUser,
ref: String,
commits: List[ApiCommit],
commits: List[ApiPushCommit],
repository: ApiRepository
) extends WebHookPayload
@@ -202,7 +202,7 @@ object WebHookService {
WebHookPushPayload(
ApiUser(pusher),
refName,
commits.map{ commit => ApiCommit(git, RepositoryName(repositoryInfo), commit) },
commits.map{ commit => ApiPushCommit(git, RepositoryName(repositoryInfo), commit) },
ApiRepository(
repositoryInfo,
owner= ApiUser(repositoryOwner)
@@ -273,7 +273,7 @@ object WebHookService {
action = "created",
repository = ApiRepository(repository, repositoryUser),
issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)),
comment = ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser)),
comment = ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
sender = ApiUser(sender))
}
}

View File

@@ -33,7 +33,7 @@ class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
case None => chain.doFilter(req, res)
case Some(Left(_)) => {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
response.setContentType("Content-Type: application/json; charset=utf-8")
response.setContentType("application/json; charset=utf-8")
val w = response.getWriter()
w.print("""{ "message": "Bad credentials" }""")
w.close()

View File

@@ -21,6 +21,17 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current BitBucket version.
*/
val versions = Seq(
new Version(3, 7) with SystemSettingsService {
override def update(conn: Connection, cl: ClassLoader): Unit = {
super.update(conn, cl)
val settings = loadSystemSettings()
if(settings.notification){
saveSystemSettings(settings.copy(useSMTP = true))
}
}
},
new Version(3, 6),
new Version(3, 5),
new Version(3, 4),
new Version(3, 3),
new Version(3, 2),
@@ -166,4 +177,4 @@ object AutoUpdate {
} else Version(0, 0)
}
}
}

View File

@@ -2,11 +2,12 @@ package gitbucket.core.servlet
import javax.servlet._
import javax.servlet.http._
import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
import gitbucket.core.util.{ControlUtil, Keys, Implicits}
import gitbucket.core.util.{Keys, Implicits}
import org.slf4j.LoggerFactory
import Implicits._
import ControlUtil._
/**
* Provides BASIC Authentication for [[GitRepositoryServlet]].
@@ -20,7 +21,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
def destroy(): Unit = {}
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
implicit val request = req.asInstanceOf[HttpServletRequest]
val request = req.asInstanceOf[HttpServletRequest]
val response = res.asInstanceOf[HttpServletResponse]
val wrappedResponse = new HttpServletResponseWrapper(response){
@@ -31,47 +32,13 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
val settings = loadSystemSettings()
try {
defining(request.paths){
case Array(_, repositoryOwner, repositoryName, _*) =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
case Some(repository) => {
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
chain.doFilter(req, wrappedResponse)
} else {
request.getHeader("Authorization") match {
case null => requireAuth(response)
case auth => decodeAuthHeader(auth).split(":", 2) match {
case Array(username, password) => {
authenticate(settings, username, password) match {
case Some(account) => {
if (isUpdating || repository.repository.isPrivate) {
if(hasWritePermission(repository.owner, repository.name, Some(account))){
request.setAttribute(Keys.Request.UserName, account.userName)
chain.doFilter(req, wrappedResponse)
} else {
requireAuth(response)
}
} else {
chain.doFilter(req, wrappedResponse)
}
}
case _ => requireAuth(response)
}
}
case _ => requireAuth(response)
}
}
}
}
case None => {
logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
}
case _ => {
logger.debug(s"Not enough path arguments: ${request.paths}")
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).map { case GitRepositoryRouting(_, _, filter) =>
// served by plug-ins
pluginRepository(request, wrappedResponse, chain, settings, isUpdating, filter)
}.getOrElse {
// default repositories
defaultRepository(request, wrappedResponse, chain, settings, isUpdating)
}
} catch {
case ex: Exception => {
@@ -81,6 +48,67 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
}
}
private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain,
settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = {
implicit val r = request
val account = for {
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield {
request.setAttribute(Keys.Request.UserName, account.userName)
account
}
if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){
chain.doFilter(request, response)
} else {
requireAuth(response)
}
}
private def defaultRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain,
settings: SystemSettings, isUpdating: Boolean): Unit = {
implicit val r = request
request.paths match {
case Array(_, repositoryOwner, repositoryName, _*) =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
case Some(repository) => {
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
chain.doFilter(request, response)
} else {
val passed = for {
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield if(isUpdating || repository.repository.isPrivate){
if(hasWritePermission(repository.owner, repository.name, Some(account))){
request.setAttribute(Keys.Request.UserName, account.userName)
true
} else false
} else true
if(passed.getOrElse(false)){
chain.doFilter(request, response)
} else {
requireAuth(response)
}
}
}
case None => {
logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
}
case _ => {
logger.debug(s"Not enough path arguments: ${request.paths}")
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
}
}
private def requireAuth(response: HttpServletResponse): Unit = {
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)

View File

@@ -1,7 +1,10 @@
package gitbucket.core.servlet
import java.io.File
import gitbucket.core.api
import gitbucket.core.model.Session
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.WebHookService._
import gitbucket.core.service._
@@ -18,7 +21,6 @@ import org.eclipse.jgit.transport.resolver._
import org.slf4j.LoggerFactory
import javax.servlet.ServletConfig
import javax.servlet.ServletContext
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
@@ -35,20 +37,8 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
override def init(config: ServletConfig): Unit = {
setReceivePackFactory(new GitBucketReceivePackFactory())
// TODO are there any other ways...?
super.init(new ServletConfig(){
def getInitParameter(name: String): String = name match {
case "base-path" => Directory.RepositoryHome
case "export-all" => "true"
case name => config.getInitParameter(name)
}
def getInitParameterNames(): java.util.Enumeration[String] = {
config.getInitParameterNames
}
def getServletContext(): ServletContext = config.getServletContext
def getServletName(): String = config.getServletName
})
val root: File = new File(Directory.RepositoryHome)
setRepositoryResolver(new GitBucketRepositoryResolver(new FileResolver[HttpServletRequest](root, true)))
super.init(config)
}
@@ -67,32 +57,52 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
}
}
class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest]) extends RepositoryResolver[HttpServletRequest] {
private val resolver = new FileResolver[HttpServletRequest](new File(Directory.GitBucketHome), true)
override def open(req: HttpServletRequest, name: String): Repository = {
// Rewrite repository path if routing is marched
PluginRegistry().getRepositoryRouting("/" + name).map { case GitRepositoryRouting(urlPattern, localPath, _) =>
val path = urlPattern.r.replaceFirstIn(name, localPath)
resolver.open(req, path)
}.getOrElse {
parent.open(req, name)
}
}
}
class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] with SystemSettingsService {
private val logger = LoggerFactory.getLogger(classOf[GitBucketReceivePackFactory])
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
val receivePack = new ReceivePack(db)
val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String]
logger.debug("requestURI: " + request.getRequestURI)
logger.debug("pusher:" + pusher)
if(PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).isEmpty){
val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String]
defining(request.paths){ paths =>
val owner = paths(1)
val repository = paths(2).stripSuffix(".git")
logger.debug("requestURI: " + request.getRequestURI)
logger.debug("pusher:" + pusher)
logger.debug("repository:" + owner + "/" + repository)
defining(request.paths){ paths =>
val owner = paths(1)
val repository = paths(2).stripSuffix(".git")
if(!repository.endsWith(".wiki")){
defining(request) { implicit r =>
val hook = new CommitLogHook(owner, repository, pusher, baseUrl)
receivePack.setPreReceiveHook(hook)
receivePack.setPostReceiveHook(hook)
logger.debug("repository:" + owner + "/" + repository)
if(!repository.endsWith(".wiki")){
defining(request) { implicit r =>
val hook = new CommitLogHook(owner, repository, pusher, baseUrl)
receivePack.setPreReceiveHook(hook)
receivePack.setPostReceiveHook(hook)
}
}
}
receivePack
}
receivePack
}
}

View File

@@ -1,12 +1,13 @@
package gitbucket.core.ssh
import gitbucket.core.model.Session
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
import gitbucket.core.servlet.{Database, CommitLogHook}
import gitbucket.core.util.{Directory, ControlUtil}
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command}
import org.slf4j.LoggerFactory
import java.io.{InputStream, OutputStream}
import java.io.{File, InputStream, OutputStream}
import ControlUtil._
import org.eclipse.jgit.api.Git
import Directory._
@@ -15,11 +16,11 @@ import org.apache.sshd.server.command.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException
object GitCommand {
val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
}
abstract class GitCommand(val owner: String, val repoName: String) extends Command {
self: RepositoryService with AccountService =>
abstract class GitCommand() extends Command {
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
protected var err: OutputStream = null
@@ -71,6 +72,11 @@ abstract class GitCommand(val owner: String, val repoName: String) extends Comma
this.in = in
}
}
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
self: RepositoryService with AccountService =>
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo)
(implicit session: Session): Boolean =
getAccountByUserName(username) match {
@@ -80,7 +86,8 @@ abstract class GitCommand(val owner: String, val repoName: String) extends Comma
}
class GitUploadPack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName)
class DefaultGitUploadPack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName)
with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -94,11 +101,10 @@ class GitUploadPack(owner: String, repoName: String, baseUrl: String) extends Gi
}
}
}
}
class GitReceivePack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName)
with SystemSettingsService with RepositoryService with AccountService {
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName)
with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
@@ -116,18 +122,56 @@ class GitReceivePack(owner: String, repoName: String, baseUrl: String) extends G
}
}
}
}
class PluginGitUploadPack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand
with SystemSettingsService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
if(routing.filter.filter("/" + repoName, Some(user), loadSystemSettings(), false)){
val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
using(Git.open(new File(Directory.GitBucketHome, path))){ git =>
val repository = git.getRepository
val upload = new UploadPack(repository)
upload.upload(in, out, err)
}
}
}
}
class PluginGitReceivePack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand
with SystemSettingsService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
if(routing.filter.filter("/" + repoName, Some(user), loadSystemSettings(), true)){
val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
using(Git.open(new File(Directory.GitBucketHome, path))){ git =>
val repository = git.getRepository
val receive = new ReceivePack(repository)
receive.receive(in, out, err)
}
}
}
}
class GitCommandFactory(baseUrl: String) extends CommandFactory {
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
override def createCommand(command: String): Command = {
import GitCommand._
logger.debug(s"command: $command")
command match {
case GitCommand.CommandRegex("upload", owner, repoName) => new GitUploadPack(owner, repoName, baseUrl)
case GitCommand.CommandRegex("receive", owner, repoName) => new GitReceivePack(owner, repoName, baseUrl)
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, baseUrl, routing(repoName))
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, baseUrl, routing(repoName))
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName, baseUrl)
case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl)
case _ => new UnknownCommand(command)
}
}
private def pluginRepository(repoName: String): Boolean = PluginRegistry().getRepositoryRouting("/" + repoName).isDefined
private def routing(repoName: String): GitRepositoryRouting = PluginRegistry().getRepositoryRouting("/" + repoName).get
}

View File

@@ -72,6 +72,8 @@ object Implicits {
def hasAttribute(name: String): Boolean = request.getAttribute(name) != null
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/")
}
implicit class RichSession(session: HttpSession){

View File

@@ -791,7 +791,7 @@ object JGitUtil {
return git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next
}
def getBranches(owner: String, name: String, defaultBranch: String): Seq[BranchInfo] = {
def getBranches(owner: String, name: String, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = {
using(Git.open(getRepositoryDir(owner, name))){ git =>
val repo = git.getRepository
val defaultObject = if (repo.getAllRefs.keySet().contains(defaultBranch)) {
@@ -802,20 +802,20 @@ object JGitUtil {
git.branchList.call.asScala.map { ref =>
val walk = new RevWalk(repo)
try{
try {
val defaultCommit = walk.parseCommit(defaultObject)
val branchName = ref.getName.stripPrefix("refs/heads/")
val branchCommit = if(branchName == defaultBranch){
defaultCommit
}else{
} else {
walk.parseCommit(ref.getObjectId)
}
val when = branchCommit.getCommitterIdent.getWhen
val committer = branchCommit.getCommitterIdent.getName
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
val mergeInfo = if(branchName==defaultBranch){
val mergeInfo = if(origin && branchName == defaultBranch){
None
}else{
} else {
walk.reset()
walk.setRevFilter( RevFilter.MERGE_BASE )
walk.markStart(branchCommit)

View File

@@ -37,7 +37,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
object Notifier {
// TODO We want to be able to switch to mock.
def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
case settings if settings.notification => new Mailer(settings.smtp.get)
case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
case _ => new MockMailer
}
@@ -75,7 +75,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
database withSession { implicit session =>
defining(
s"[${r.name}] ${issue.title} (#${issue.issueId})" ->
msg(Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
msg(Markdown.toHtml(content, r, false, true, false))) { case (subject, msg) =>
recipients(issue) { to =>
val email = new HtmlEmail
email.setHostName(smtp.host)
@@ -87,7 +87,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
email.setSSLOnConnect(ssl)
}
smtp.fromAddress
.map (_ -> smtp.fromName.orNull)
.map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName))
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
.foreach { case (address, name) =>
email.setFrom(address, name)

View File

@@ -20,7 +20,7 @@ object StringUtil {
md.digest.map(b => "%02x".format(b)).mkString
}
def urlEncode(value: String): String = URLEncoder.encode(value, "UTF-8")
def urlEncode(value: String): String = URLEncoder.encode(value, "UTF-8").replace("+", "%20")
def urlDecode(value: String): String = URLDecoder.decode(value, "UTF-8")

View File

@@ -6,7 +6,7 @@ import org.scalatra.i18n.Messages
trait Validations {
/**
* Constraint for the identifier such as user name, repository name or page name.
* Constraint for the identifier such as user name or page name.
*/
def identifier: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
@@ -19,6 +19,23 @@ trait Validations {
}
}
/**
* Constraint for the repository identifier.
*/
def repository: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
if(!value.matches("[a-zA-Z0-9\\-\\+_.]+")){
Some(s"${name} contains invalid character.")
} else if(value.startsWith("_") || value.startsWith("-")){
Some(s"${name} starts with invalid character.")
} else {
None
}
}
/**
* Constraint for the color pattern.
*/
def color = pattern("#[0-9a-fA-F]{6}")
/**

View File

@@ -10,28 +10,65 @@ trait LinkConverter { self: RequestCache =>
* Converts issue id, username and commit id to link.
*/
protected def convertRefsLinks(value: String, repository: RepositoryService.RepositoryInfo,
issueIdPrefix: String = "#", escapeHtml: Boolean = true)(implicit context: Context): String = {
issueIdPrefix: String = "#", escapeHtml: Boolean = true)(implicit context: Context): String = {
// escape HTML tags
val escaped = if(escapeHtml) value.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;") else value
escaped
// convert issue id to link
.replaceBy(("(?<=(^|\\W))" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r){ m =>
getIssue(repository.owner, repository.name, m.group(2)) match {
case Some(issue) if(issue.isPullRequest)
=> Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(2)}">#${m.group(2)}</a>""")
case Some(_) => Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/issues/${m.group(2)}">#${m.group(2)}</a>""")
case None => Some(s"""#${m.group(2)}""")
// convert username/project@SHA to link
.replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r){ m =>
getAccountByUserName(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group(3)}@${m.group(4).substring(0, 7)}</a>"""
}
}
// convert username/project#Num to link
.replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r){ m =>
getIssue(m.group(2), m.group(3), m.group(4)) match {
case Some(issue) if (issue.isPullRequest)
=> Some( s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group(3)}#${m.group(4)}</a>""")
case Some(_) => Some( s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/issues/${m.group(4)}">${m.group(2)}/${m.group(3)}#${m.group(4)}</a>""")
case None => Some( s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""")
}
}
// convert username@SHA to link
.replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r ) { m =>
getAccountByUserName(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m.group(3).substring(0, 7)}</a>"""
}
}
// convert username#Num to link
.replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r ) { m =>
getIssue(m.group(2), repository.name, m.group(3)) match {
case Some(issue) if(issue.isPullRequest)
=> Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m.group(3)}</a>""")
case Some(_) => Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/issues/${m.group(3)}">${m.group(2)}#${m.group(3)}</a>""")
case None => Some(s"""${m.group(2)}#${m.group(3)}""")
}
}
// convert issue id to link
.replaceBy(("(?<=(^|\\W))(GH-|" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m =>
val prefix = if(m.group(2) == "issue:") "#" else m.group(2)
getIssue(repository.owner, repository.name, m.group(3)) match {
case Some(issue) if(issue.isPullRequest)
=> Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m.group(3)}</a>""")
case Some(_) => Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/issues/${m.group(3)}">${prefix}${m.group(3)}</a>""")
case None => Some(s"""${m.group(2)}${m.group(3)}""")
}
}
// convert @username to link
.replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_]+)(?=(\\W|$))".r){ m =>
.replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r){ m =>
getAccountByUserName(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}">@${m.group(2)}</a>"""
}
}
// convert commit id to link
.replaceAll("(?<=(^|\\W))([a-f0-9]{40})(?=(\\W|$))", s"""<a href="${context.path}/${repository.owner}/${repository.name}/commit/$$2">$$2</a>""")
.replaceAll("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))", s"""<a href="${context.path}/${repository.owner}/${repository.name}/commit/$$2">$$2</a>""")
}
}

View File

@@ -1,32 +1,36 @@
package gitbucket.core.view
import java.text.Normalizer
import java.util.Locale
import java.util.regex.Pattern
import java.util.Locale
import gitbucket.core.controller.Context
import gitbucket.core.service.{RepositoryService, RequestCache, WikiService}
import gitbucket.core.service.{RepositoryService, RequestCache}
import gitbucket.core.util.StringUtil
import org.parboiled.common.StringUtils
import org.pegdown.LinkRenderer.Rendering
import org.pegdown._
import org.pegdown.ast._
import scala.collection.JavaConverters._
import io.github.gitbucket.markedj._
import io.github.gitbucket.markedj.Utils._
object Markdown {
/**
* Converts Markdown of Wiki pages to HTML.
*
* @param repository the repository which contains the markdown
* @param enableWikiLink if true then wiki style link is available in markdown
* @param enableRefsLink if true then issue reference (e.g. #123) is rendered as link
* @param enableAnchor if true then anchor for headline is generated
* @param enableTaskList if true then task list syntax is available
* @param hasWritePermission true if user has writable to ths given repository
* @param pages the list of existing Wiki pages
*/
def toHtml(markdown: String,
repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean,
enableRefsLink: Boolean,
enableAnchor: Boolean,
enableTaskList: Boolean = false,
hasWritePermission: Boolean = false,
pages: List[String] = Nil)(implicit context: Context): String = {
// escape issue id
val s = if(enableRefsLink){
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
@@ -34,228 +38,146 @@ object Markdown {
// escape task list
val source = if(enableTaskList){
GitBucketHtmlSerializer.escapeTaskList(s)
escapeTaskList(s)
} else s
val rootNode = new PegDownProcessor(
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML
).parseMarkdown(source.toCharArray)
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages).toHtml(rootNode)
val options = new Options()
options.setSanitize(true)
val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
Marked.marked(source, options, renderer)
}
}
class GitBucketLinkRender(
context: Context,
repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean,
pages: List[String]) extends LinkRenderer with WikiService {
/**
* Extends markedj Renderer for GitBucket
*/
class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean,
pages: List[String])
(implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache {
override def render(node: WikiLinkNode): Rendering = {
if(enableWikiLink){
try {
val text = node.getText
val (label, page) = if(text.contains('|')){
val i = text.indexOf('|')
(text.substring(0, i), text.substring(i + 1))
override def heading(text: String, level: Int, raw: String): String = {
val id = generateAnchorName(text)
val out = new StringBuilder()
out.append("<h" + level + " id=\"" + options.getHeaderPrefix + id + "\" class=\"markdown-head\">")
if(enableAnchor){
out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"></a>")
out.append("<a class=\"markdown-anchor\" name=\"" + id + "\"></a>")
}
out.append(text)
out.append("</h" + level + ">\n")
out.toString()
}
override def code(code: String, lang: String, escaped: Boolean): String = {
"<pre class=\"prettyprint" + (if(lang != null) s" ${options.getLangPrefix}${lang}" else "" )+ "\">" +
(if(escaped) code else escape(code, true)) + "</pre>"
}
override def list(body: String, ordered: Boolean): String = {
var listType: String = null
if (ordered) {
listType = "ol"
}
else {
listType = "ul"
}
if(body.contains("""class="task-list-item-checkbox"""")){
return "<" + listType + " class=\"task-list\">\n" + body + "</" + listType + ">\n"
} else {
return "<" + listType + ">\n" + body + "</" + listType + ">\n"
}
}
override def listitem(text: String): String = {
if(text.contains("""class="task-list-item-checkbox" """)){
return "<li class=\"task-list-item\">" + text + "</li>\n"
} else {
return "<li>" + text + "</li>\n"
}
}
override def text(text: String): String = {
// convert commit id and username to link.
val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:", false) else text
// convert task list to checkbox.
val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1
t2
}
override def link(href: String, title: String, text: String): String = {
super.link(fixUrl(href, true), title, text)
}
override def image(href: String, title: String, text: String): String = {
super.image(fixUrl(href, true), title, text)
}
override def nolink(text: String): String = {
if(enableWikiLink && text.startsWith("[[") && text.endsWith("]]")){
val link = text.replaceAll("(^\\[\\[|\\]\\]$)", "")
val (label, page) = if(link.contains('|')){
val i = link.indexOf('|')
(link.substring(0, i), link.substring(i + 1))
} else {
(text, text)
(link, link)
}
val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
if(pages.contains(page)){
new Rendering(url, label)
"<a href=\"" + url + "\">" + escape(label) + "</a>"
} else {
new Rendering(url, label).withAttribute("class", "absent")
"<a href=\"" + url + "\" class=\"absent\">" + escape(label) + "</a>"
}
} catch {
case e: java.io.UnsupportedEncodingException => throw new IllegalStateException
}
} else {
super.render(node)
}
}
}
class GitBucketVerbatimSerializer extends VerbatimSerializer {
def serialize(node: VerbatimNode, printer: Printer): Unit = {
printer.println.print("<pre")
if (!StringUtils.isEmpty(node.getType)) {
printer.print(" class=").print('"').print("prettyprint ").print(node.getType).print('"')
}
printer.print(">")
var text: String = node.getText
while (text.charAt(0) == '\n') {
printer.print("<br/>")
text = text.substring(1)
}
printer.printEncoded(text)
printer.print("</pre>")
}
}
class GitBucketHtmlSerializer(
markdown: String,
repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean,
enableRefsLink: Boolean,
enableTaskList: Boolean,
hasWritePermission: Boolean,
pages: List[String]
)(implicit val context: Context) extends ToHtmlSerializer(
new GitBucketLinkRender(context, repository, enableWikiLink, pages),
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
) with LinkConverter with RequestCache {
override protected def printImageTag(imageNode: SuperNode, url: String): Unit = {
printer.print("<a target=\"_blank\" href=\"").print(fixUrl(url, true)).print("\">")
.print("<img src=\"").print(fixUrl(url, true)).print("\" alt=\"").printEncoded(printChildrenToString(imageNode)).print("\"/></a>")
}
override protected def printLink(rendering: LinkRenderer.Rendering): Unit = {
printer.print('<').print('a')
printAttribute("href", fixUrl(rendering.href))
for (attr <- rendering.attributes.asScala) {
printAttribute(attr.name, attr.value)
}
printer.print('>').print(rendering.text).print("</a>")
}
private def fixUrl(url: String, isImage: Boolean = false): String = {
if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){
url
} else if(url.startsWith("#")){
("#" + GitBucketHtmlSerializer.generateAnchorName(url.substring(1)))
} else if(!enableWikiLink){
if(context.currentPath.contains("/blob/")){
url + (if(isImage) "?raw=true" else "")
} else if(context.currentPath.contains("/tree/")){
val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
} else {
val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
escape(text)
}
} else {
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
}
}
private def printAttribute(name: String, value: String): Unit = {
printer.print(' ').print(name).print('=').print('"').print(value).print('"')
}
private def printHeaderTag(node: HeaderNode): Unit = {
val tag = s"h${node.getLevel}"
val headerTextString = printChildrenToString(node)
val anchorName = GitBucketHtmlSerializer.generateAnchorName(headerTextString)
printer.print(s"""<$tag class="markdown-head">""")
printer.print(s"""<a class="markdown-anchor-link" href="#$anchorName"></a>""")
printer.print(s"""<a class="markdown-anchor" name="$anchorName"></a>""")
visitChildren(node)
printer.print(s"</$tag>")
}
override def visit(node: HeaderNode): Unit = {
printHeaderTag(node)
}
override def visit(node: TextNode): Unit = {
// convert commit id and username to link.
val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
// convert task list to checkbox.
val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t
if (abbreviations.isEmpty) {
printer.print(text)
} else {
printWithAbbreviations(text)
private def fixUrl(url: String, isImage: Boolean = false): String = {
if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){
url
} else if(url.startsWith("#")){
("#" + generateAnchorName(url.substring(1)))
} else if(!enableWikiLink){
if(context.currentPath.contains("/blob/")){
url + (if(isImage) "?raw=true" else "")
} else if(context.currentPath.contains("/tree/")){
val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
} else {
val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
}
} else {
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
}
}
}
override def visit(node: VerbatimNode) {
val printer = new Printer()
val serializer = verbatimSerializers.get(VerbatimSerializer.DEFAULT)
serializer.serialize(node, printer)
val html = printer.getString
// convert commit id and username to link.
val t = if(enableRefsLink) convertRefsLinks(html, repository, "issue:", escapeHtml = false) else html
this.printer.print(t)
}
override def visit(node: BulletListNode): Unit = {
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
printer.println().print("""<ul class="task-list">""").indent(+2)
visitChildren(node)
printer.indent(-2).println().print("</ul>")
} else {
printIndentedTag(node, "ul")
}
}
override def visit(node: ListItemNode): Unit = {
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
printer.println()
printer.print("""<li class="task-list-item">""")
visitChildren(node)
printer.print("</li>")
} else {
printer.println()
printTag(node, "li")
}
}
override def visit(node: ExpLinkNode) {
printLink(linkRenderer.render(node, printLinkChildrenToString(node)))
}
def printLinkChildrenToString(node: SuperNode) = {
val priorPrinter = printer
printer = new Printer()
visitLinkChildren(node)
val result = printer.getString()
printer = priorPrinter
result
}
def visitLinkChildren(node: SuperNode) {
import scala.collection.JavaConversions._
node.getChildren.foreach(child => child match {
case node: ExpImageNode => visitLinkChild(node)
case node: SuperNode => visitLinkChildren(node)
case _ => child.accept(this)
})
}
def visitLinkChild(node: ExpImageNode) {
printer.print("<img src=\"").print(fixUrl(node.url, true)).print("\" alt=\"").printEncoded(printChildrenToString(node)).print("\"/>")
}
}
object GitBucketHtmlSerializer {
private val Whitespace = "[\\s]".r
def generateAnchorName(text: String): String = {
val noWhitespace = Whitespace.replaceAllIn(text, "-")
val normalized = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD)
val noSpecialChars = StringUtil.urlEncode(normalized)
noSpecialChars.toLowerCase(Locale.ENGLISH)
}
def escapeTaskList(text: String): String = {
Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ")
}
def generateAnchorName(text: String): String = {
val normalized = Normalizer.normalize(text.replaceAll("<.*>", "").replaceAll("[\\s]", "-"), Normalizer.Form.NFD)
val encoded = StringUtil.urlEncode(normalized)
encoded.toLowerCase(Locale.ENGLISH)
}
def convertCheckBox(text: String, hasWritePermission: Boolean): String = {
val disabled = if (hasWritePermission) "" else "disabled"
text.replaceAll("task:x:", """<input type="checkbox" class="task-list-item-checkbox" checked="checked" """ + disabled + "/>")
.replaceAll("task: :", """<input type="checkbox" class="task-list-item-checkbox" """ + disabled + "/>")
.replaceAll("task: :", """<input type="checkbox" class="task-list-item-checkbox" """ + disabled + "/>")
}
}

View File

@@ -5,7 +5,7 @@ import java.util.{Date, Locale, TimeZone}
import gitbucket.core.controller.Context
import gitbucket.core.model.CommitState
import gitbucket.core.plugin.{RenderRequest, PluginRegistry, Renderer}
import gitbucket.core.plugin.{RenderRequest, PluginRegistry}
import gitbucket.core.service.{RepositoryService, RequestCache}
import gitbucket.core.util.{FileUtil, JGitUtil, StringUtil}
@@ -91,16 +91,16 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
enableTaskList: Boolean = false,
hasWritePermission: Boolean = false,
pages: List[String] = Nil)(implicit context: Context): Html =
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages))
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, true, enableTaskList, hasWritePermission, pages))
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: Context): Html = {
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = {
val fileName = filePath.reverse.head.toLowerCase
val extension = FileUtil.getExtension(fileName)
val renderer = PluginRegistry().getRenderer(extension)
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context))
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context))
}
def isRenderable(fileName: String): Boolean = {
@@ -155,6 +155,11 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
.replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]", (m: Match) => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}</a>""")
)
/**
* Remove html tags from the given Html instance.
*/
def removeHtml(html: Html): Html = Html(html.body.replaceAll("<.+?>", ""))
/**
* URL encode except '/'.
*/

View File

@@ -14,9 +14,9 @@
</div>
<div class="block">
@if(account.url.isDefined){
<div><i class="icon-home"></i> <a href="@account.url">@account.url</a></div>
<div><i class="octicon octicon-home"></i> <a href="@account.url">@account.url</a></div>
}
<div><i class="icon-time"></i> <span class="muted">Joined on</span> @date(account.registeredDate)</div>
<div><i class="octicon octicon-clock"></i> <span class="muted">Joined on</span> @date(account.registeredDate)</div>
</div>
@if(groupNames.nonEmpty){
<div>

View File

@@ -13,7 +13,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:void(0);" data-name="@loginAccount.get.userName"><i class="icon-ok"></i> <span>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span></a></li>
<li><a href="javascript:void(0);" data-name="@loginAccount.get.userName"><i class="octicon octicon-check"></i> <span>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span></a></li>
@groupNames.map { groupName =>
<li><a href="javascript:void(0);" data-name="@groupName"><i class="icon-white"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
}
@@ -31,7 +31,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
<fieldset class="margin">
<label class="radio">
<input type="radio" name="isPrivate" value="false" @if(isCreateRepoOptionPublic){checked}>
<span class="strong"><img src="@assets/common/images/repo_public.png"/>&nbsp;</i>&nbsp;Public</span><br>
<span class="strong"><i class="octicon octicon-repo"></i>&nbsp;</i>&nbsp;Public</span><br>
<div>
<span>All users and guests can read this repository.</span>
</div>
@@ -40,7 +40,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
<fieldset>
<label class="radio">
<input type="radio" name="isPrivate" value="true" @if(!isCreateRepoOptionPublic){checked}>
<span class="strong"><img src="@assets/common/images/repo_private.png"/>&nbsp;</i>&nbsp;Private</span><br>
<span class="strong"><i class="octicon octicon-lock"></i>&nbsp;</i>&nbsp;Private</span><br>
<div>
<span>Only collaborators can read this repository.</span>
</div>

View File

@@ -16,7 +16,7 @@
<div class="block-header">
<a href="@url(repository)">@repository.name</a>
@if(repository.repository.isPrivate){
<i class="icon-lock"></i>
<i class="octicon octicon-lock"></i>
}
</div>
@if(repository.repository.originUserName.isDefined){

View File

@@ -3,19 +3,20 @@
<div class="container">
<div class="row-fluid">
<div class="span3">
<div class="box">
<ul class="nav nav-tabs nav-stacked side-menu">
<li@if(active=="users"){ class="active"}>
<a href="@path/admin/users">User Management</a>
</li>
<li@if(active=="system"){ class="active"}>
<a href="@path/admin/system">System Settings</a>
</li>
<li>
<a href="@path/console/login.jsp">H2 Console</a>
</li>
</ul>
</div>
<ul class="nav nav-tabs nav-stacked side-menu" id="system-admin-menu-container">
<li@if(active=="users"){ class="active"}>
<a href="@path/admin/users">User Management</a>
</li>
<li@if(active=="system"){ class="active"}>
<a href="@path/admin/system">System Settings</a>
</li>
<li@if(active=="plugins"){ class="active"}>
<a href="@path/admin/plugins">Plugins</a>
</li>
<li>
<a href="@path/console/login.jsp">H2 Console</a>
</li>
</ul>
</div>
<div class="span9">
@body

View File

@@ -0,0 +1,30 @@
@(plugins: List[gitbucket.core.plugin.PluginInfo])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Plugins"){
@admin.html.menu("plugins") {
<h1>Installed plugins</h1>
@if(plugins.size > 0) {
<ul>
@plugins.map {plugin =>
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.version</a></li>
}
</ul>
@plugins.map {plugin =>
<div class="box">
<div class="box-header">@plugin.pluginName</div>
<div class="box-content">
<p><span class="strong">Id:&nbsp;</span>@plugin.pluginId</p>
<p><span class="strong">Version:&nbsp;</span>@plugin.version</p>
<p><span class="strong">Name:&nbsp;</span>@plugin.pluginName</p>
<p class="muted">@plugin.description</p>
</div>
</div>
}
} else {
<p>No plugin detected on your gitbucket installation.</p>
}
}
}

View File

@@ -224,14 +224,25 @@
<!-- Notification email -->
<!--====================================================================-->
<hr>
<label class="strong">Notification email</label>
<label class="strong">Notifications</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="notification" name="notification"@if(settings.notification){ checked}/>
Send notifications
</label>
</fieldset>
<div class="form-horizontal notification">
<!--====================================================================-->
<!-- Communication email -->
<!--====================================================================-->
<hr>
<label class="strong">Communication</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="useSMTP" name="useSMTP" @if(settings.useSMTP){ checked}/>
SMTP
</label>
</fieldset>
<div class="form-horizontal useSMTP">
<div class="control-group">
<label class="control-label" for="smtpHost">SMTP Host</label>
<div class="controls">
@@ -277,12 +288,15 @@
<input type="text" id="fromName" name="smtp.fromName" value="@settings.smtp.map(_.fromName)"/>
</div>
</div>
<p class="muted">
Enable notification not only SMTP configuration if you want to send nitification email.
</p>
</div>
</div>
</div>
<fieldset>
<div class="align-right" style="margin-top: 20px;">
<input type="submit" class="btn btn-success" value="Apply changes"/>
</fieldset>
</div>
</form>
}
}
@@ -292,8 +306,16 @@ $(function(){
$('.ssh input').prop('disabled', !$(this).prop('checked'));
}).change();
$('#notification').change(function(){
$('.notification input').prop('disabled', !$(this).prop('checked'));
$('#useSMTP').change(function(){
$('.useSMTP input').prop('disabled', !$(this).prop('checked'));
// With only SMTP in current version, notification cannot be enabled if no communication channel exists
$('#notification').prop('disabled', !$(this).prop('checked'));
if (!$(this).prop('checked')) {
// With only SMTP in current version, if SMTP is unchecked then we disable notification
$('#notification').prop('checked', false);
}
}).change();
$('#ldapAuthentication').change(function(){

View File

@@ -43,10 +43,10 @@
<div>
<hr>
@if(!account.isGroupAccount){
<i class="icon-envelope"></i> @account.mailAddress
<i class="octicon octicon-mail"></i> @account.mailAddress
}
@account.url.map { url =>
<i class="icon-home"></i> @url
<i class="octicon octicon-home"></i> @url
}
</div>
<div>

View File

@@ -6,11 +6,11 @@
@import gitbucket.core.view.helpers._
<span class="small">
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL">
<img src="@assets/common/images/status-open@(if(condition.state == "open"){"-active"}).png"/>
<i class="octicon octicon-issue-opened @(if(condition.state == "open"){"active"})"></i>
@openCount Open
</a>&nbsp;&nbsp;
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
<img src="@assets/common/images/status-closed@(if(condition.state == "closed"){"-active"}).png"/>
<i class="octicon octicon-check @(if(condition.state == "closed"){"active"})"></i>
@closedCount Closed
</a>
</span>

View File

@@ -19,9 +19,9 @@
<tr>
<td style="padding-top: 15px; padding-bottom: 15px;">
@if(issue.isPullRequest){
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
<i class="octicon octicon-git-pull-request @(if(issue.closed) "closed" else "open")"></i>
} else {
<img src="@assets/common/images/issue-@(if(issue.closed) "closed" else "open").png"/>
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")"></i>
}
<a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;
@if(issue.isPullRequest){
@@ -39,18 +39,19 @@
}
@if(commentCount > 0){
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
<img src="@assets/common/images/comment-active.png"> @commentCount
<i class="octicon octicon-comment active"></i> @commentCount
</a>
} else {
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
<img src="@assets/common/images/comment.png"> @commentCount
<i class="octicon octicon-comment"></i> @commentCount
</a>
}
</span>
<div class="small muted" style="margin-left: 20px; margin-top: 5px;">
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
@milestone.map { milestone =>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><img src="@assets/common/images/milestone.png"> @milestone</a></span>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i>
@milestone</a></span>
}
</div>
</td>

View File

@@ -4,16 +4,16 @@
<div class="dashboard-nav">
<div class="container">
<a href="@path/" @if(active == ""){ class="active"}>
<img src="@assets/common/images/menu-feed.png">
<i class="octicon octicon-rss"></i>
News Feed
</a>
@if(loginAccount.isDefined){
<a href="@path/dashboard/pulls" @if(active == "pulls" ){ class="active"}>
<img src="@assets/common/images/menu-pulls.png">
<i class="octicon octicon-git-pull-request"></i>
Pull Requests
</a>
<a href="@path/dashboard/issues" @if(active == "issues"){ class="active"}>
<img src="@assets/common/images/menu-issues.png">
<i class="octicon octicon-issue-opened"></i>
Issues
</a>
}
@@ -27,6 +27,8 @@ div.dashboard-nav {
margin-bottom: 20px;
}
div.dashboard-nav a {
line-height: 10px;
margin-left: 20px;
@@ -36,8 +38,17 @@ div.dashboard-nav a {
color: #888;
}
div.dashboard-nav a:hover {
div.dashboard-nav .octicon{
width: 16px;
height: 16px;
font-size: 16px;
color: #888;
}
div.dashboard-nav a:hover,div.dashboard-nav a:hover .octicon {
text-decoration: none;
color: #333;
}
div.dashboard-nav a.active {

View File

@@ -8,20 +8,20 @@
@activities.map { activity =>
<div class="block">
@(activity.activityType match {
case "open_issue" => detailActivity(activity, "activity-issue.png")
case "comment_issue" => detailActivity(activity, "activity-comment.png")
case "comment_commit" => detailActivity(activity, "activity-comment.png")
case "close_issue" => detailActivity(activity, "activity-issue-close.png")
case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png")
case "open_pullreq" => detailActivity(activity, "activity-merge.png")
case "merge_pullreq" => detailActivity(activity, "activity-merge.png")
case "create_repository" => simpleActivity(activity, "activity-create-repository.png")
case "create_branch" => simpleActivity(activity, "activity-branch.png")
case "delete_branch" => simpleActivity(activity, "activity-delete.png")
case "create_tag" => simpleActivity(activity, "activity-tag.png")
case "delete_tag" => simpleActivity(activity, "activity-delete.png")
case "fork" => simpleActivity(activity, "activity-fork.png")
case "push" => customActivity(activity, "activity-commit.png"){
case "open_issue" => detailActivity(activity, "issue-opened")
case "comment_issue" => detailActivity(activity, "comment-discussion")
case "comment_commit" => detailActivity(activity, "comment-discussion")
case "close_issue" => detailActivity(activity, "issue-closed")
case "reopen_issue" => detailActivity(activity, "issue-reopened")
case "open_pullreq" => detailActivity(activity, "git-pull-request")
case "merge_pullreq" => detailActivity(activity, "git-merge")
case "create_repository" => simpleActivity(activity, "repo")
case "create_branch" => simpleActivity(activity, "git-branch")
case "delete_branch" => simpleActivity(activity, "circle-slash")
case "create_tag" => simpleActivity(activity, "tag")
case "delete_tag" => simpleActivity(activity, "circle-slash")
case "fork" => simpleActivity(activity, "repo-forked")
case "push" => customActivity(activity, "git-commit"){
<div class="small activity-message">
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
if(i == 3){
@@ -37,12 +37,12 @@
}}
</div>
}
case "create_wiki" => customActivity(activity, "activity-wiki.png"){
case "create_wiki" => customActivity(activity, "book"){
<div class="small activity-message">
Created <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
</div>
}
case "edit_wiki" => customActivity(activity, "activity-wiki.png"){
case "edit_wiki" => customActivity(activity, "book"){
activity.additionalInfo.get.split(":") match {
case Array(pageName, commitId) =>
<div class="small activity-message">
@@ -61,7 +61,7 @@
}
@detailActivity(activity: gitbucket.core.model.Activity, image: String) = {
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
<div class="activity-content">
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@@ -75,7 +75,7 @@
}
@customActivity(activity: gitbucket.core.model.Activity, image: String)(additionalInfo: Any) = {
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
<div class="activity-content">
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@@ -87,7 +87,7 @@
}
@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = {
<div class="activity-icon-small"><img src="@assets/common/images/@image"/></div>
<div class="activity-icon-small"><i class="octicon octicon-@image"></i></div>
<div class="activity-content">
<div>
@avatar(activity.activityUserName, 16)

View File

@@ -8,7 +8,7 @@
@helper.html.dropdown(
value = if(branch.length == 40) branch.substring(0, 10) else branch,
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
mini = true
mini = false
) {
<li><div id="branch-control-title">Switch branches<button id="branch-control-close" class="pull-right">&times</button></div></li>
<li><input id="branch-control-input" type="text" placeholder="Find or create branch ..."/></li>

View File

@@ -1,6 +1,6 @@
@(condition: => Boolean)
@if(condition){
<i class="icon-ok"></i>
<i class="octicon octicon-check"></i>
} else {
<i class="icon-white"></i>
}

View File

@@ -5,10 +5,13 @@
@import context._
@import gitbucket.core._
@import gitbucket.core.view.helpers._
<div class="@if(comment.fileName.isDefined && (!latestCommitId.isDefined || latestCommitId.get == comment.commitId)){inline-comment}" @if(comment.fileName.isDefined){filename=@comment.fileName.get} @if(comment.newLine.isDefined){newline=@comment.newLine.get} @if(comment.oldLine.isDefined){oldline=@comment.oldLine.get}>
<div class="@if(comment.fileName.isDefined && (!latestCommitId.isDefined || latestCommitId.get == comment.commitId)){inline-comment}"
@if(comment.fileName.isDefined){filename="@comment.fileName.get"}
@if(comment.newLine.isDefined){newline="@comment.newLine.get"}
@if(comment.oldLine.isDefined){oldline="@comment.oldLine.get"}>
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="box commit-comment-box commit-comment-@comment.commentId">
<div class="box-header-small">
<div class="box-header">
@user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
commented
@@ -24,12 +27,12 @@
</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false)){
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a>
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-pencil"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x"></i></a>
}
</span>
</div>
<div class="box-content commit-commentContent-@comment.commentId markdown-body">
<div class="box-content-bottom commit-commentContent-@comment.commentId markdown-body">
@markdown(comment.content, repository, false, true, true, hasWritePermission)
</div>
</div>

View File

@@ -1,7 +1,7 @@
@(id: String, value: String)(html: Html)
<div class="input-append" style="margin-bottom: 0px;">
@(id: String, value: String, prepend: Boolean = false)(html: Html)
<div class="input-append @if(prepend){input-prepend}" style="margin-bottom: 0px;">
@html
<span id="@id" class="add-on btn" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="icon-check"></i></span>
<span id="@id" class="add-on btn" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
</div>
<script>
// copy to clipboard

View File

@@ -2,7 +2,7 @@
@import gitbucket.core.view.helpers
<div id="@name" class="input-append date" data-date-format="yyyy-mm-dd" data-date="@value.map(helpers.date)">
<input class="span2" name="@name" type="text" readonly="" value="@value.map(helpers.date)" size="16"/>
<span class="add-on"><i class="icon-calendar"></i></span>
<span class="add-on"><i class="octicon octicon-calendar"></i></span>
</div>
<script>
$(function(){

View File

@@ -25,16 +25,16 @@
<span class="pull-right diffstat" data-diff-id="@i"></span>
<a href="#diff-@i">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
<i class="octicon octicon-diff-renamed"></i> @diff.oldPath -> @diff.newPath
}
@if(diff.changeType == ChangeType.ADD){
<img src="@assets/common/images/diff_add.png"/> @diff.newPath
<i class="octicon octicon-diff-added"></i> @diff.newPath
}
@if(diff.changeType == ChangeType.MODIFY){
<img src="@assets/common/images/diff_edit.png"/> @diff.newPath
<i class="octicon octicon-diff-modified"></i> @diff.newPath
}
@if(diff.changeType == ChangeType.DELETE){
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
<i class="octicon octicon-diff-removed"></i> @diff.oldPath
}
</a>
</li>
@@ -55,7 +55,7 @@
</div>
}
<span class="diffstat">
<img src="@assets/common/images/diff_move.png"/>
<i class="octicon octicon-diff-renamed"></i>
</span> @diff.oldPath -> @diff.newPath
}
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
@@ -68,9 +68,9 @@
}
<span class="diffstat">
@if(diff.changeType == ChangeType.ADD){
<img src="@assets/common/images/diff_add.png"/>
<i class="octicon octicon-diff-added"></i>
}else{
<img src="@assets/common/images/diff_edit.png"/>
<i class="octicon octicon-diff-modified"></i>
}
</span>
@diff.newPath
@@ -83,7 +83,7 @@
</div>
}
<span class="diffstat">
<img src="@assets/common/images/diff_delete.png"/>
<i class="octicon octicon-diff-removed"></i>
</span> @diff.oldPath
}
</th>

View File

@@ -9,7 +9,7 @@
@if(flat){style="border: none; background-color: #eee;"}
class="dropdown-toggle @if(!flat){btn} else {flat} @if(mini){btn-mini} else {btn-small}" data-toggle="dropdown">
@if(value.isEmpty){
<i class="icon-cog"></i>
<i class="octicon octicon-gear"></i>
} else {
@if(prefix.nonEmpty){
<span class="muted">@prefix:</span>

View File

@@ -16,7 +16,7 @@
<published>@datetimeRFC3339(activity.activityDate)</published>
<updated>@datetimeRFC3339(activity.activityDate)</updated>
<link type="text/html" rel="alternate" href="@context.baseUrl/@activity.userName/@activity.repositoryName" />
<title type="html">@activity.activityType</title>
<title type="html">@removeHtml(activityMessage(activity.message))</title>
<author>
<name>@activity.activityUserName</name>
<uri>@url(activity.activityUserName)</uri>

View File

@@ -5,6 +5,7 @@
enableTaskList: Boolean,
hasWritePermission: Boolean,
style: String = "",
styleClass: String = "",
placeholder: String = "Leave a comment",
elastic: Boolean = false,
uid: Long = new java.util.Date().getTime())(implicit context: gitbucket.core.controller.Context)
@@ -20,7 +21,9 @@
<div class="tab-pane active" id="tab@uid">
<span id="error-content" class="error"></span>
@textarea = {
<textarea id="content@uid" name="content"@if(style.nonEmpty){ style="@style"} placeholder="@placeholder">@content</textarea>
<textarea id="content@uid" name="content" placeholder="@placeholder"
@if(style.nonEmpty){ style="@style"}
@if(styleClass.nonEmpty){ class="@styleClass" }>@content</textarea>
}
@if(enableWikiLink){
@textarea

View File

@@ -3,11 +3,11 @@
@import context._
@import gitbucket.core.view.helpers._
@if(repository.repository.isPrivate){
<img src="@assets/common/images/repo_private@{if(large){"_lg"}}.png"/>
<i class="@{if(large){"mega-"}}octicon octicon-lock"></i>
} else {
@if(repository.repository.originUserName.isDefined){
<img src="@assets/common/images/repo_fork@{if(large){"_lg"}}.png"/>
<i class="@{if(large){"mega-"}}octicon octicon-repo-forked"></i>
} else {
<img src="@assets/common/images/repo_public@{if(large){"_lg"}}.png"/>
<i class="@{if(large){"mega-"}}octicon octicon-repo"></i>
}
}

View File

@@ -24,57 +24,64 @@
@if(loginAccount.isEmpty){
@signinform(settings)
} else {
<table class="table table-bordered">
<tr>
<th class="metal">
<div class="pull-right">
<a href="@path/new" class="btn btn-success btn-mini">New repository</a>
</div>
Your repositories (@userRepositories.size)
</th>
</tr>
<div class="box-header">
<div class="pull-right">
<a href="@path/new" class="btn btn-success btn-mini">New repository</a>
</div>
<span class="strong">Your repositories</span> <span class="label">@userRepositories.size</span>
</div>
@if(userRepositories.isEmpty){
<tr>
<td>No repositories</td>
</tr>
<div class="box-content-bottom">
No repositories
</div>
} else {
@userRepositories.map { repository =>
<tr>
<td>
@helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
}
</td>
</tr>
}
<div class="box-content-bottom" style="padding: 0px;">
@defining(3){ max =>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<div class="box-content-row repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
}
</div>
}
@if(userRepositories.size > max){
<div class="box-content-row show-more">
<a href="javascript:void(0);" id="show-more-repos">Show more @{userRepositories.size - max} pages...</a>
</div>
}
}
</div>
}
</table>
}
<table class="table table-bordered">
<tr>
<th class="metal">
Recent updated repositories
</th>
</tr>
@if(recentRepositories.isEmpty){
<tr>
<td>No repositories</td>
</tr>
} else {
<div class="box-header">
<span class="strong">Recent updated repositories</span>
</div>
@if(recentRepositories.isEmpty){
<div class="box-content-bottom">
No repositories
</div>
} else {
<div class="box-content-bottom" style="padding: 0px;">
@recentRepositories.map { repository =>
<tr>
<td>
@helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</td>
</tr>
<div class="box-content-row repo-link">
@helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</div>
}
}
</table>
</div>
}
</div>
</div>
</div>
}
<script>
$(function(){
$('#show-more-repos').click(function(e){
$(e.target).parents('div.box-content-bottom').find('div.repo-link').show();
$(e.target).parents('div.show-more').remove();
});
});
</script>

View File

@@ -8,18 +8,27 @@
<hr/><br/>
<form method="POST" validate="true">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="box issue-comment-box">
<div class="issue-comment-box">
<div class="box-content">
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 635px; height: 100px; max-height: 150px;", elastic = true)
@helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
enableRefsLink = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
style = "",
elastic = true
)
<div style="text-align: right;">
<input type="hidden" name="issueId" value="@issue.issueId"/>
<input type="submit" class="btn btn-success" formaction="@url(repository)/issue_comments/new" value="Comment"/>
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == loginAccount.get.userName)){
<input type="submit" class="btn" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
}
</div>
</div>
</div>
<div class="pull-right">
<input type="hidden" name="issueId" value="@issue.issueId"/>
<input type="submit" class="btn btn-success" formaction="@url(repository)/issue_comments/new" value="Comment"/>
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == loginAccount.get.userName)){
<input type="submit" class="btn" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
}
</div>
</form>
}
<script>

View File

@@ -8,16 +8,16 @@
@import gitbucket.core.model.CommitComment
@if(issue.isDefined){
<div class="issue-avatar-image">@avatar(issue.get.openedUserName, 48)</div>
<div class="box issue-comment-box">
<div class="box-header-small">
<div class="issue-comment-box">
<div class="box-header">
@user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.get.registeredDate)</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.get.issueId"><i class="icon-pencil" aria-label="Edit"></i></a>
<a href="#" data-issue-id="@issue.get.issueId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
}
</span>
</div>
<div class="box-content issue-content markdown-body" id="issueContent">
<div class="box-content-bottom issue-content markdown-body" id="issueContent">
@markdown(issue.get.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
</div>
</div>
@@ -28,7 +28,7 @@
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="box issue-comment-box" id="comment-@comment.commentId">
<div class="box-header-small">
<div class="box-header">
@user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
@if(comment.action == "comment"){
@@ -41,12 +41,12 @@
<span class="pull-right">
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
&& (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil" aria-label="Edit"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle" aria-label="Remove"></i></a>
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
}
</span>
</div>
<div class="box-content"class="issue-content" id="commentContent-@comment.commentId">
<div class="box-content-bottom issue-content" id="commentContent-@comment.commentId">
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
@defining(comment.content.substring(comment.content.length - 40)){ id =>
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
@@ -78,8 +78,8 @@
</div>
}
@if(comment.action == "close" || comment.action == "close_comment"){
<div class="small issue-comment-action">
<span class="label label-important">Closed</span>
<div class="issue-comment-action">
<i class="octicon octicon-circle-slash danger"></i>
@avatar(comment.commentedUserName, 20)
@if(issue.isDefined && issue.get.isPullRequest){
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
@@ -89,14 +89,14 @@
</div>
}
@if(comment.action == "reopen" || comment.action == "reopen_comment"){
<div class="small issue-comment-action">
<span class="label label-success">Reopened</span>
<div class="issue-comment-action issue-reopened">
<i class="octicon octicon-primitive-dot"></i>
@avatar(comment.commentedUserName, 20)
@user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
</div>
}
@if(comment.action == "delete_branch"){
<div class="small issue-comment-action">
<div class="issue-comment-action">
<span class="label">Deleted</span>
@avatar(comment.commentedUserName, 20)
@user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @helper.html.datetimeago(comment.registeredDate)
@@ -110,7 +110,7 @@
<script>
$(function(){
@if(issue.isDefined){
$('.issue-comment-box i.icon-pencil').click(function(){
$('.issue-comment-box i.octicon-pencil').click(function(){
var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/issue_comments/_data/' + id;
var $content = $('#commentContent-' + id);
@@ -130,7 +130,7 @@ $(function(){
});
return false;
});
$('.issue-comment-box i.icon-remove-circle').click(function(){
$('.issue-comment-box i.octicon-x').click(function(){
if(confirm('Are you sure you want to delete this?')) {
var id = $(this).closest('a').data('comment-id');
$.post('@url(repository)/issue_comments/delete/' + id,
@@ -144,7 +144,7 @@ $(function(){
return false;
});
}
$(document).on('click', '.commit-comment-box i.icon-pencil', function(){
$(document).on('click', '.commit-comment-box i.octicon-pencil', function(){
var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/commit_comments/_data/' + id;
var $content = $('.commit-commentContent-' + id, $(this).closest('.box'));
@@ -158,7 +158,7 @@ $(function(){
});
return false;
});
$(document).on('click', '.commit-comment-box i.icon-remove-circle', function(){
$(document).on('click', '.commit-comment-box i.octicon-x', function(){
if(confirm('Are you sure you want to delete this?')) {
var id = $(this).closest('a').data('comment-id');
$.post('@url(repository)/commit_comments/delete/' + id,

View File

@@ -10,139 +10,33 @@
@navigation("issues", false, repository)
<br/><br/><hr style="margin-bottom: 10px;">
<form action="@url(repository)/issues/new" method="POST" validate="true">
<div class="row-fluid">
<div class="span9">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="box issue-box">
<div class="box-content">
<span id="error-title" class="error"></span>
<input type="text" name="title" value="" placeholder="Title" style="width: 565px;" autofocus/>
<div>
<span id="label-assigned">No one is assigned</span>
@if(hasWritePermission){
<input type="hidden" name="assignedUserName" value=""/>
@helper.html.dropdown() {
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
@collaborators.map { collaborator =>
<li><a href="javascript:void(0);" class="assign" data-name="@collaborator"><i class="icon-while"></i>@avatar(collaborator, 20) @collaborator</a></li>
}
}
}
<div class="pull-right">
<span id="label-milestone">No milestone</span>
@if(hasWritePermission){
<input type="hidden" name="milestoneId" value=""/>
@helper.html.dropdown() {
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
<li>
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
<i class="icon-while"></i> @milestone.title
<div class="small" style="padding-left: 20px;">
@milestone.dueDate.map { dueDate =>
@if(isPast(dueDate)){
<img src="@assets/common/images/alert.png"/><span class="milestone-alert">Due by @date(dueDate)</span>
} else {
<span class="muted">Due by @date(dueDate)</span>
}
}.getOrElse {
<span class="muted">No due date</span>
}
</div>
</a>
</li>
}
}
}
<div class="row-fluid">
<div class="span10">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="box issue-box">
<div class="box-content">
<span id="error-title" class="error"></span>
<input type="text" name="title" value="" placeholder="Title" style="width: 690px;" autofocus/>
@helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
enableRefsLink = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
style = "width: 690px; height: 200px; max-height: 250px;",
elastic = true
)
<div class="align-right">
<input type="submit" class="btn btn-success" value="Submit new issue"/>
</div>
</div>
<hr>
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 565px; height: 200px; max-height: 250px;", elastic = true)
</div>
</div>
<div class="pull-right">
<input type="submit" class="btn btn-success" value="Submit new issue"/>
<div class="span2">
@issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository)
</div>
</div>
<div class="span3">
@if(hasWritePermission){
<span class="strong">Labels</span>
<div>
<div id="label-list">
<ul class="label-list nav nav-pills nav-stacked">
@labels.map { label =>
<li>
<a href="javascript:void(0);" class="toggle-label" data-label="@label.labelName" data-bgcolor="@label.color" data-fgcolor="@label.fontColor">
<span style="background-color: #@label.color;" class="label-color">&nbsp;&nbsp;</span>
@label.labelName
</a>
</li>
}
</ul>
<input type="hidden" name="labelNames" value=""/>
</div>
</div>
}
</div>
</div>
</form>
}
}
<script>
$(function(){
$('a.assign').click(function(){
var userName = $(this).data('name');
$('a.assign i.icon-ok').attr('class', 'icon-white');
if(userName == ''){
$('#label-assigned').text('No one will be assigned');
} else {
$('#label-assigned').html($('<span>')
.append($('<a class="username strong">').attr('href', '@path/' + userName).text(userName))
.append(' will be assigned'));
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
}
$('input[name=assignedUserName]').val(userName);
});
$('a.milestone').click(function(){
var title = $(this).data('title');
var milestoneId = $(this).data('id');
$('a.milestone i.icon-ok').attr('class', 'icon-white');
if(milestoneId == ''){
$('#label-milestone').text('No milestone');
} else {
$('#label-milestone').html($('<span class="strong">').text(title));
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
}
$('input[name=milestoneId]').val(milestoneId);
});
$('a.toggle-label').click(function(){
if($(this).data('selected') == true){
$(this).css({
'background-color': 'white',
'color' : 'black',
'font-weight' : 'normal'
});
$(this).data('selected', false);
} else {
$(this).css({
'background-color': '#' + $(this).data('bgcolor'),
'color' : '#' + $(this).data('fgcolor'),
'font-weight' : 'bold'
});
$(this).data('selected', true);
}
var labelNames = Array();
$('a.toggle-label').each(function(i, e){
if($(e).data('selected') == true){
labelNames.push($(e).data('label'));
}
});
$('input[name=labelNames]').val(labelNames.join(','));
});
});
</script>

View File

@@ -2,7 +2,7 @@
@import context._
<span id="error-edit-content-@commentId" class="error"></span>
@helper.html.attached(owner, repository){
<textarea style="width: 635px; height: 100px;" id="edit-content-@commentId">@content</textarea>
<textarea id="edit-content-@commentId">@content</textarea>
}
<div>
<input type="button" id="cancel-comment-@commentId" class="btn btn-small btn-danger" value="Cancel"/>

View File

@@ -1,7 +1,7 @@
@(content: Option[String], issueId: Int, owner: String, repository: String)(implicit context: gitbucket.core.controller.Context)
@import context._
@helper.html.attached(owner, repository){
<textarea style="width: 635px; height: 100px; max-height: 300px;" id="edit-content">@content.getOrElse("")</textarea>
<textarea id="edit-content">@content.getOrElse("")</textarea>
}
<div>
<input type="button" id="cancel-issue" class="btn btn-small btn-danger" value="Cancel"/>

View File

@@ -13,9 +13,9 @@
<div>
<div class="show-title pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
<a class="btn btn-small" href="#" id="edit">Edit</a>
<a class="btn" href="#" id="edit">Edit</a>
}
<a class="btn btn-small btn-success" href="@url(repository)/issues/new">New issue</a>
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
</div>
<div class="edit-title pull-right" style="display: none;">
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>
@@ -45,13 +45,14 @@
</span>
<br/><br/>
<hr>
<br/>
<div class="row-fluid">
<div class="span10">
@commentlist(Some(issue), comments, hasWritePermission, repository)
@commentform(issue, true, hasWritePermission, repository)
</div>
<div class="span2">
@issueinfo(issue, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
@issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div>
</div>
}

View File

@@ -1,4 +1,4 @@
@(issue: gitbucket.core.model.Issue,
@(issue: Option[gitbucket.core.model.Issue],
comments: List[gitbucket.core.model.Comment],
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
@@ -6,6 +6,7 @@
labels: List[gitbucket.core.model.Label],
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
<div style="margin-bottom: 8px;">
<span class="muted small strong">Labels</span>
@@ -22,6 +23,9 @@
</li>
}
}
@if(issue.isEmpty){
<input type="hidden" name="labelNames" value=""/>
}
</div>
}
</div>
@@ -34,15 +38,18 @@
@if(hasWritePermission){
<div class="pull-right">
@helper.html.dropdown(right = true) {
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="octicon octicon-x"></i> Clear this milestone</a></li>
@milestones.filter(_._1.closedDate.isEmpty).map { case (milestone, _, _) =>
<li>
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
@helper.html.checkicon(Some(milestone.milestoneId) == issue.milestoneId) @milestone.title
@issue.map { issue =>
@helper.html.checkicon(Some(milestone.milestoneId) == issue.milestoneId)
}
@milestone.title
<div class="small" style="padding-left: 20px;">
@milestone.dueDate.map { dueDate =>
@if(isPast(dueDate)){
<img src="@assets/common/images/alert.png"/><span class="milestone-alert">Due by @date(dueDate)</span>
<i class="octicon octicon-alert" style="color:#BD2C00;"></i><span class="milestone-alert">Due by @date(dueDate)</span>
} else {
<span class="muted">Due by @date(dueDate)</span>
}
@@ -58,14 +65,14 @@
}
</div>
<div id="milestone-progress-area">
@issue.milestoneId.map { milestoneId =>
@issue.flatMap(_.milestoneId).map { milestoneId =>
@milestones.collect { case (milestone, openCount, closeCount) if(milestone.milestoneId == milestoneId) =>
@issues.milestones.html.progress(openCount + closeCount, closeCount)
}
}
</div>
<span id="label-milestone">
@issue.milestoneId.map { milestoneId =>
@issue.flatMap(_.milestoneId).map { milestoneId =>
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
<span class="strong small">@milestone.title</span>
}
@@ -73,17 +80,20 @@
<span class="muted small">No milestone</span>
}
</span>
@if(issue.isEmpty){
<input type="hidden" name="milestoneId" value=""/>
}
<hr/>
<div style="margin-bottom: 8px;">
<span class="muted small strong">Assignee</span>
@if(hasWritePermission){
<div class="pull-right">
@helper.html.dropdown(right = true) {
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
@collaborators.map { collaborator =>
<li>
<a href="javascript:void(0);" class="assign" data-name="@collaborator">
@helper.html.checkicon(Some(collaborator) == issue.assignedUserName)@avatar(collaborator, 20) @collaborator
@helper.html.checkicon(issue.exists(_.assignedUserName == collaborator))@avatar(collaborator, 20) @collaborator
</a>
</li>
}
@@ -92,39 +102,37 @@
}
</div>
<span id="label-assigned">
@issue.assignedUserName.map { userName =>
@issue.flatMap(_.assignedUserName).map { userName =>
@avatar(userName, 20) @user(userName, styleClass="username strong small")
}.getOrElse{
<span class="muted small">No one</span>
}
</span>
<hr/>
<div style="margin-bottom: 8px;">
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
<div class="muted small strong">@participants.size @plural(participants.size, "participant")</div>
@participants.map { participant => @avatarLink(participant, 20, tooltip = true) }
}
</div>
@if(issue.isEmpty){
<input type="hidden" name="assignedUserName" value=""/>
}
@issue.map { issue =>
<hr/>
<div style="margin-bottom: 8px;">
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
<div class="muted small strong">@participants.size @plural(participants.size, "participant")</div>
@participants.map { participant =>
@avatarLink(participant, 20, tooltip = true)
}
}
</div>
}
<script>
$(function(){
@issue.map { issue =>
$('a.toggle-label').click(function(){
var path, icon;
var i = $(this).children('i');
if(i.hasClass('icon-ok')){
path = 'delete';
icon = 'icon-white';
} else {
path = 'new';
icon = 'icon-ok';
}
var path = switchLabel($(this));
$.post('@url(repository)/issues/@issue.issueId/label/' + path,
{
labelId : $(this).data('label-id')
},
function(data){
i.removeClass().addClass(icon);
$('ul.label-list').empty().html(data);
});
{ labelId : $(this).data('label-id') },
function(data){
$('ul.label-list').empty().html(data);
}
);
return false;
});
@@ -132,42 +140,92 @@ $(function(){
var title = $(this).data('title');
var milestoneId = $(this).data('id');
$.post('@url(repository)/issues/@issue.issueId/milestone',
{
milestoneId: milestoneId
},
function(data){
console.log(data);
$('a.milestone i.icon-ok').attr('class', 'icon-white');
if(milestoneId == ''){
$('#label-milestone').html($('<span class="muted small">').text('No milestone'));
$('#milestone-progress-area').empty();
} else {
$('#label-milestone').html($('<span class="strong small">').text(title));
$('#milestone-progress-area').html(data);
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
{ milestoneId: milestoneId },
function(data){
displayMilestone(title, milestoneId, data);
}
});
);
});
$('a.assign').click(function(){
var $this = $(this);
var userName = $this.data('name');
$.post('@url(repository)/issues/@issue.issueId/assign',
{
assignedUserName: userName
},
function(){
$('a.assign i.icon-ok').attr('class', 'icon-white');
if(userName == ''){
$('#label-assigned').html($('<span class="muted small">').text('No one'));
} else {
$('#label-assigned').empty()
.append($this.find('img.avatar-mini').clone(false)).append(' ')
.append($('<a class="username strong small">').attr('href', '@context.path/' + userName).text(userName));
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
{ assignedUserName: userName },
function(){
displayAssignee($this, userName);
}
);
});
}.getOrElse {
$('a.toggle-label').click(function(){
switchLabel($(this));
var labelNames = Array();
$('a.toggle-label').each(function(i, e){
if($(e).children('i').hasClass('icon-ok') == true){
labelNames.push($(e).text().trim());
}
});
$('input[name=labelNames]').val(labelNames.join(','));
$.post('@url(repository)/issues/new/label',
{ labelNames : labelNames.join(',') },
function(data){
$('ul.label-list').empty().html(data);
}
);
});
$('a.milestone').click(function(){
var title = $(this).data('title');
var milestoneId = $(this).data('id');
displayMilestone(title, milestoneId);
$('input[name=milestoneId]').val(milestoneId);
});
$('a.assign').click(function(){
var $this = $(this);
var userName = $this.data('name');
displayAssignee($this, userName);
$('input[name=assignedUserName]').val(userName);
});
}
function switchLabel($this){
var i = $this.children('i');
if(i.hasClass('icon-ok')){
i.removeClass().addClass('icon-white');
return 'delete';
} else {
i.removeClass().addClass('icon-ok');
return 'new';
}
}
function displayMilestone(title, milestoneId, progress){
$('a.milestone i.icon-ok').attr('class', 'icon-white');
if(milestoneId == ''){
$('#label-milestone').html($('<span class="muted small">').text('No milestone'));
$('#milestone-progress-area').empty();
} else {
$('#label-milestone').html($('<span class="strong small">').text(title));
if(progress){
$('#milestone-progress-area').html(progress);
}
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
}
}
function displayAssignee($this, userName){
$('a.assign i.icon-ok').attr('class', 'icon-white');
if(userName == ''){
$('#label-assigned').html($('<span class="muted small">').text('No one'));
} else {
$('#label-assigned').empty()
.append($this.find('img.avatar-mini').clone(false)).append(' ')
.append($('<a class="username strong small">').attr('href', '@context.path/' + userName).text(userName));
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
}
}
});
</script>

View File

@@ -11,7 +11,7 @@
<div style="margin-top: 6px">
<a href="@url(repository)/issues?labels=@urlEncode(label.labelName)" id="label-row-content-@label.labelId">
<span style="background-color: #@label.color; color: #@label.fontColor; padding: 8px; font-size: 120%; border-radius: 4px;">
<img src="@assets/common/images/label_@(if(label.fontColor == "ffffff") "white" else "black").png" style="width: 12px;"/>
<i class="octicon octicon-tag" style="color: #@label.fontColor;"></i>
@label.labelName
</span>
</a>

View File

@@ -16,8 +16,7 @@
@if(condition.nonEmpty){
<div>
<a href="@gitbucket.core.service.IssuesService.IssueSearchCondition().toURL" class="header-link">
<img src="@assets/common/images/clear.png" class="header-icon"/>
<img src="@assets/common/images/clear_hover.png" class="header-icon-hover" style="display: none;"/>
<i class="octicon octicon-x" ></i>
<span class="strong">Clear current search query, filters, and sorts</span>
</a>
</div>
@@ -28,11 +27,11 @@
<input type="checkbox"/>
<span class="small">
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL">
<img src="@assets/common/images/status-open@(if(condition.state == "open"){"-active"}).png"/>
<i class="octicon octicon-issue-opened @(if(condition.state == "open"){"active"})"></i>
@openCount Open
</a>&nbsp;&nbsp;
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
<img src="@assets/common/images/status-closed@(if(condition.state == "closed"){"-active"}).png"/>
<i class="octicon octicon-check @(if(condition.state == "closed"){"active"})"></i>
@closedCount Closed
</a>
</span>
@@ -139,7 +138,7 @@
}
}
@helper.html.dropdown("Assignee", flat = true) {
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
@collaborators.map { collaborator =>
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="icon-white"></i>@avatar(collaborator, 20) @collaborator</a></li>
}
@@ -176,7 +175,7 @@
@if(hasWritePermission){
<input type="checkbox" value="@issue.issueId"/>
}
<img src="@assets/common/images/issue-@(if(issue.closed) "closed" else "open").png" style="margin-right: 20px;"/>
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i>
@if(repository.isEmpty){
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65;
}
@@ -195,18 +194,19 @@
}
@if(commentCount > 0){
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
<img src="@assets/common/images/comment-active.png"> @commentCount
<i class="octicon octicon-comment active"></i> @commentCount
</a>
} else {
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
<img src="@assets/common/images/comment.png"> @commentCount
<i class="octicon octicon-comment"></i> @commentCount
</a>
}
</span>
<div class="small muted" style="margin-left: 40px; margin-top: 5px;">
#@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
@milestone.map { milestone =>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><img src="@assets/common/images/milestone.png"> @milestone</a></span>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i>
@milestone</a></span>
}
</div>
</td>
@@ -216,4 +216,3 @@
<div class="pull-right">
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), gitbucket.core.service.IssuesService.IssueLimit, 10, condition.toURL)
</div>

View File

@@ -13,11 +13,11 @@
<th style="background-color: #eee;">
<span class="small">
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
<img src="@assets/common/images/milestone@(if(state == "open"){"-active"}).png"/>
<i class="octicon octicon-milestone @(if(state == "open"){"active"})"></i>
@milestones.count(_._1.closedDate.isEmpty) Open
</a>&nbsp;&nbsp;
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
<img src="@assets/common/images/milestone@(if(state == "closed"){"-active"}).png"/>
<i class="octicon octicon-milestone @(if(state == "closed"){"active"})"></i>
@milestones.count(_._1.closedDate.isDefined) Closed
</a>
</span>
@@ -38,7 +38,7 @@
} else {
@milestone.dueDate.map { dueDate =>
@if(isPast(dueDate)){
<img src="@assets/common/images/alert.png"/><span class="muted milestone-alert">Due by @date(dueDate)</span>
<i class="octicon octicon-alert" style="color:#BD2C00;"></i><span class="muted milestone-alert">Due by @date(dueDate)</span>
} else {
<span class="muted">Due by @date(dueDate)</span>
}

View File

@@ -13,6 +13,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="@assets/vendors/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href="@assets/vendors/bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
<link href="@assets/vendors/octicons/octicons.css" rel="stylesheet">
<link href="@assets/vendors/datepicker/css/datepicker.css" rel="stylesheet">
<link href="@assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
<link href="@assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
@@ -40,7 +41,7 @@
<form id="search" action="@path/search" method="POST">
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<div class="container" style="width: 980px;">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@@ -51,27 +52,39 @@
@defining(AutoUpdate.getCurrentVersion){ version =>
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
}
</a>
@repository.map { repository =>
<input type="text" name="query" style="width: 200px; margin-top: 5px; margin-bottom: 0px;" placeholder="Search this repository"/>
<input type="hidden" name="owner" value="@repository.owner"/>
<input type="hidden" name="repository" value="@repository.name"/>
}
@if(loginAccount.isDefined){
<a href="@path/dashboard/pulls" class="global-header-menu">Pull requests</a>
<a href="@path/dashboard/issues" class="global-header-menu">Issues</a>
}
<div class="nav-collapse collapse pull-right header-menu">
@repository.map { repository =>
<input type="text" name="query" style="width: 300px; margin-bottom: 0px;" placeholder="Search this repository"/>
<input type="hidden" name="owner" value="@repository.owner"/>
<input type="hidden" name="repository" value="@repository.name"/>
}
@if(loginAccount.isDefined){
<a href="@url(loginAccount.get.userName)" class="username menu">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</a>
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#"><i class="icon-plus"></i><span class="caret" style="vertical-align: middle;"></span></a>
<ul class="dropdown-menu">
<li><a href="@path/new">New repository</a></li>
<li><a href="@path/groups/new">New group</a></li>
</ul>
<a href="@url(loginAccount.get.userName)/_edit" class="menu" data-toggle="tooltip" data-placement="bottom" title="Account settings"><i class="icon-user" aria-label="Account settings"></i></a>
@if(loginAccount.get.isAdmin){
<a href="@path/admin/users" class="menu" data-toggle="tooltip" data-placement="bottom" title="Administration"><i class="icon-wrench" aria-label="Administration"></i></a>
}
<a href="@path/signout" class="menu-last" data-toggle="tooltip" data-placement="bottom" title="Sign out"><i class="icon-share-alt" aria-label="Sign out"></i></a>
<div class="btn-group" style="margin-top: 0px;">
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#"><i class="octicon octicon-plus" style="font-size: 20px; vertical-align: middle;height:20px !important;"></i><span class="caret" style="vertical-align: middle;"></span></a>
<ul class="dropdown-menu pull-right">
<li><a href="@path/new">New repository</a></li>
<li><a href="@path/groups/new">New group</a></li>
</ul>
</div>
<div class="btn-group" style="margin-top: 0px;">
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">@avatar(loginAccount.get.userName, 16)<span class="caret" style="vertical-align: middle;"></span></a>
<ul class="dropdown-menu pull-right">
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
@if(loginAccount.get.isAdmin){
<li><a href="@path/admin/users">System administration</a></li>
}
<li><a href="@path/signout">Sign out</a></li>
</ul>
</div>
} else {
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-last" id="signin">Sign in</a>
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn" id="signin">Sign in</a>
}
</div><!--/.nav-collapse -->
</div>

View File

@@ -9,16 +9,10 @@
@import context._
@import gitbucket.core.view.helpers._
@sidemenu(path: String, name: String, label: String, count: Int = 0) = {
@sidemenu(path: String, name: String, icon: String, label: String, count: Int = 0) = {
<li @if(active == name){class="active"} @if(!expand){data-toggle="tooltip" data-placement="left" data-original-title="@label"}>
<div class="@if(active == name){margin} else {gradient} pull-left"></div>
<a href="@url(repository)@path">
@if(active == name){
<img src="@assets/common/images/menu-@{name}-active.png">
} else {
<img src="@assets/common/images/menu-@{name}-active.png" class="menu-icon-active" style="display:none;">
<img src="@assets/common/images/menu-@{name}.png" class="menu-icon">
}
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i>
@if(expand){ @label}
@if(expand && count > 0){
<div class="pull-right"><span class="label">@count</span></div>
@@ -27,13 +21,6 @@
</li>
}
@sidemenuPlugin(path: String, name: String, label: String, icon: String) = {
<li @if(active == name){class="active"}>
<div class="@if(active == name){margin} else {gradient} pull-left"></div>
<a href="@url(repository)@path"><img src="@icon"/>@if(expand){ @label}</a>
</li>
}
<div class="container">
@helper.html.information(info)
@helper.html.error(error)
@@ -41,7 +28,7 @@
<div class="pull-right">
<div class="input-prepend">
@if(loginAccount.isEmpty){
<a title="You must be signed in to fork a repository" href="@path/signin" class="btn btn-small" style="margin-bottom: 10px;">Fork</a>
<a title="You must be signed in to fork a repository" href="@path/signin?redirect=@urlEncode(s"${path}/${repository.owner}/${repository.name}")" class="btn btn-small" style="margin-bottom: 10px;">Fork</a>
} else {
@if(isNoGroup) {
<a id="fork-link" href="javascript:void(0);" class="btn btn-small" style="margin-bottom: 10px;">Fork</a>
@@ -51,12 +38,12 @@
}
<span class="add-on count"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
</div>
@if(loginAccount.isDefined && isNoGroup){
<form id="fork-form" method="post" action="@path/@repository.owner/@repository.name/fork" style="display: none;">
<input type="hidden" name="account" value="@loginAccount.get.userName"/>
</form>
}
</div>
@if(loginAccount.isDefined && isNoGroup){
<form id="fork-form" method="post" action="@path/@repository.owner/@repository.name/fork">
<input type="hidden" name="account" value="@loginAccount.get.userName"/>
</form>
}
}
<div class="head">
@helper.html.repositoryicon(repository, true)
@@ -76,12 +63,12 @@
<div style="width: @if(expand){170px} else {40px};" class="pull-right">
<ul class="sidemenu">
<li style="height: 12px"><div class="gradient pull-left" style="height: 12px"></div></li>
@sidemenu("" , "code" , "Code")
@sidemenu("/issues", "issues", "Issues", repository.issueCount)
@sidemenu("/pulls" , "pulls" , "Pull Requests", repository.pullCount)
@sidemenu("/wiki" , "wiki" , "Wiki")
@sidemenu("" , "code" , "code" , "Code")
@sidemenu("/issues", "issues" , "issue-opened" , "Issues", repository.issueCount)
@sidemenu("/pulls" , "pulls" , "git-pull-request" , "Pull Requests", repository.pullCount)
@sidemenu("/wiki" , "wiki" , "book" , "Wiki")
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
@sidemenu("/settings", "settings", "Settings")
@sidemenu("/settings" , "settings" , "tools", "Settings")
}
<li style="height: 12px"><div class="gradient pull-left" style="height: 12px"></div></li>
</ul>
@@ -98,12 +85,19 @@
</div>
}
@id.map { id =>
@if(context.platform != "linux" && context.platform != null){
<div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download ZIP</a>
<a href="@repository.httpOpenRepoUrl(context.platform)" id="repository-clone-url" class="btn btn-small" style="width: 147px;font-weight: bold;"><i class="octicon octicon-desktop-download"></i>&nbsp;&nbsp;Clone in Desktop</a>
</div>
}
<div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download TAR.GZ</a>
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;font-weight: bold;"><i class="octicon octicon-cloud-download"></i>Download ZIP</a>
</div>
@*
<div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-small" style="width: 147px;font-weight: bold;"><i class="octicon octicon-cloud-download"></i>Download TAR.GZ</a>
</div>
*@
}
}
</div>
@@ -112,27 +106,24 @@
@repository.repository.description.map { description =>
<p class="description">@description</p>
}
<div style="border: 1px solid #eee; padding: 4px; margin-bottom: 10px;">
<div style="margin-bottom: 10px;" class="box-content">
<table class="fill-width">
<tr>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link">
<img src="@assets/common/images/header-commits-hover.png" class="header-icon-hover" style="display: none;"/>
<img src="@assets/common/images/header-commits.png" class="header-icon"/>
<i class="octicon octicon-history"></i>
<strong>@repository.commitCount</strong> commits
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/branches" class="header-link" class="header-link">
<img src="@assets/common/images/header-branches-hover.png" class="header-icon-hover" style="display: none;"/>
<img src="@assets/common/images/header-branches.png" class="header-icon"/>
<i class="octicon octicon-git-branch"></i>
<strong>@repository.branchList.length</strong> branches
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/tags" class="header-link" class="header-link">
<img src="@assets/common/images/header-tags-hover.png" class="header-icon-hover" style="display: none;"/>
<img src="@assets/common/images/header-tags.png" class="header-icon"/>
<i class="octicon octicon-tag"></i>
<strong>@repository.tags.length</strong> releases
</a>
</td>
@@ -145,44 +136,21 @@
</div>
<script>
$(function(){
$('a.header-link').mouseover(function(e){
var target = e.target;
if(e.target.tagName != 'A'){
target = e.target.parentElement;
}
$(target).children('strong' ).css('color', '#0088cc');
$(target).children('img.header-icon-hover').css('display', 'inline');
$(target).children('img.header-icon' ).css('display', 'none');
});
$('a.header-link').mouseout(function(e){
var target = e.target;
if(e.target.tagName != 'A'){
target = e.target.parentElement;
}
$(target).children('strong' ).css('color', 'black');
$(target).children('img.header-icon-hover').css('display', 'none');
$(target).children('img.header-icon' ).css('display', 'inline');
});
$('ul.sidemenu a').mouseover(function(e){
var target = e.target;
if(e.target.tagName == "IMG"){
if(e.target.tagName == "I"){
target = e.target.parentElement;
}
$(target).prev ('div.gradient' ).css('border-left', '1px solid silver');
$(target).children('img.menu-icon-active').css('display', 'inline');
$(target).children('img.menu-icon' ).css('display', 'none');
});
$('ul.sidemenu a').mouseout(function(e){
var target = e.target;
if(e.target.tagName == "IMG"){
if(e.target.tagName == "I"){
target = e.target.parentElement;
}
$(target).prev ('div.gradient' ).css('border-left', '1px solid #eee');
$(target).children('img.menu-icon-active').css('display', 'none');
$(target).children('img.menu-icon' ).css('display', 'inline');
});
$('a[rel*=facebox]').facebox({
@@ -209,12 +177,14 @@ $(function(){
$('#repository-url-http').click(function(){
$('#repository-url-proto').text('HTTP');
$('#repository-url').val('@repository.httpUrl');
$('#repository-clone-url').attr('href', '@repository.httpOpenRepoUrl(context.platform)')
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
$('#repository-url-ssh').click(function(){
$('#repository-url-proto').text('SSH');
$('#repository-url').val('@repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)');
$('#repository-clone-url').attr('href', '@repository.sshOpenRepoUrl(context.platform, settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
}

View File

@@ -4,32 +4,42 @@
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.model._
<div class="box">
<table class="table table-file-list" style="border: 1px solid silver;">
@commits.map { day =>
<tr>
<th colspan="4" class="box-header" style="font-weight: normal;">@date(day.head.commitTime)</th>
</tr>
@day.map { commit =>
<tr>
<td style="width: 20%;">
@avatar(commit, 20)
@user(commit.authorName, commit.authorEmailAddress, "username")
</td>
<td>@commit.shortMessage</td>
<td style="width: 10%; text-align: right">
<span class="badge" style="display: inline">@if(comments.isDefined){
@comments.get.flatMap @{
case comment: CommitComment => Some(comment)
case other => None
}.count(t => t.commitId == commit.id && !t.pullRequest)
}</span>
</td>
<td style="width: 10%; text-align: right;">
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
</td>
</tr>
}
<div class="commit-list">
@commits.map { day =>
<div class="muted" style="background-color: white;">
<i class="octicon octicon-git-commit"></i> Commits on @date(day.head.commitTime)
</div>
<div class="box-commits">
@day.map { commit =>
<div class="box-content-row" style="padding: 8px;">
<ul class="nav nav-pills-group pull-right" style="margin-top: 2px; margin-bottom: 0px; margin-right: 4px;">
<li class="first"><a href="@url(repository)/commit/@commit.id" class="link monospace">@commit.id.substring(0, 7)</a></li>
<li class="last"><a href="@url(repository)/tree/@commit.id" style="padding-top: 9px; padding-bottom: 10px;"><i class="octicon octicon-code link"></i></a></li>
</ul>
<div>
<div class="commit-avatar-image">@avatar(commit, 40)</div>
<div class="commit-message-box">
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
}
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div class="small">
@user(commit.authorName, commit.authorEmailAddress, "username")
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
@if(commit.isDifferentFromAuthor) {
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
}
</div>
</div>
</div>
</div>
}
</table>
</div>
}
</div>

View File

@@ -9,18 +9,16 @@
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
hasOriginWritePermission: Boolean,
collaborators: List[String],
milestones: List[gitbucket.core.model.Milestone],
labels: List[gitbucket.core.model.Label])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main(s"Pull Requests - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("pulls", repository){
<div class="pullreq-info">
<div id="compare-info">
<a href="#" id="edit-compare-condition" class="btn btn-mini pull-right">Edit</a>
<span class="label label-info monospace">@originRepository.owner:@originId</span> ... <span class="label label-info monospace">@forkedRepository.owner:@forkedId</span>
</div>
<div id="compare-edit" style="display: none;">
<a href="#" id="cancel-condition-editing" class="pull-right"><i class="icon-remove-circle"></i></a>
<div id="compare-edit">
@helper.html.dropdown(originRepository.owner + "/" + originRepository.name, "base fork") {
@members.map { case (owner, name) =>
<li><a href="#" class="origin-owner" data-owner="@owner" data-name="@name">@helper.html.checkicon(owner == originRepository.owner) @owner/@name</a></li>
@@ -43,33 +41,52 @@
}
}
</div>
</div>
@if(commits.nonEmpty && hasWritePermission){
<div style="margin-bottom: 10px;" id="create-pull-request">
<a href="#" class="btn btn-success" id="show-form">Create pull request</a>
<div class="check-conflict" style="display: none;">
<img src="@assets/common/images/indicator.gif"/> Checking...
</div>
<div id="pull-request-form" class="box" style="display: none;">
<div class="box-content">
<form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div style="width: 240px; position: absolute; margin-left: 610px;">
<div class="check-conflict" style="display: none;">
<img src="@assets/common/images/indicator.gif"/> Checking...
</div>
@if(commits.nonEmpty && loginAccount.isDefined){
<div style="margin-bottom: 10px; padding: 8px; background-color: #fff9ea" id="create-pull-request" class="box-content">
<a href="#" class="btn btn-success" id="show-form">Create pull request</a>
&nbsp;&nbsp;
<span class="muted">Discuss and review the changes in this comparison with others.</span>
</div>
<div id="pull-request-form" @*class="box"*@ style="display: none; margin-bottom: 20px;">
<form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div class="row-fluid">
<div class="span10">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="box issue-box">
<div class="box-content">
<span class="error" id="error-title"></span>
<input type="text" name="title" style="width: 690px" placeholder="Title"/>
@helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
enableRefsLink = true,
enableTaskList = true,
hasWritePermission = true,
style = "width: 690px; height: 200px;"
)
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
<input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/>
<input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="commitIdFrom" value="@sourceId"/>
<input type="hidden" name="commitIdTo" value="@commitId"/>
<div class="align-right">
<input type="submit" class="btn btn-success" value="Create pull request"/>
</div>
</div>
</div>
<div style="width: 600px; border-right: 1px solid #d4d4d4;">
<span class="error" id="error-title"></span>
<input type="text" name="title" style="width: 580px" placeholder="Title"/>
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 580px; height: 200px;")
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
<input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/>
<input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="commitIdFrom" value="@sourceId"/>
<input type="hidden" name="commitIdTo" value="@commitId"/>
</div>
</form>
</div>
<div class="span2">
@issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), labels, hasOriginWritePermission, repository)
</div>
</div>
</form>
</div>
}
@if(commits.isEmpty){
@@ -82,72 +99,119 @@
</tr>
</table>
} else {
@pulls.html.commits(commits, Some(comments), repository)
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true, None, hasWritePermission, false)
<div style="margin-bottom: 10px;" class="box-content">
<table class="fill-width">
<tr>
<td style="width: 25%; text-align: center;">
<i class="octicon octicon-commit"></i>
@defining(commits.flatten){ commits =>
<strong>@commits.size</strong> @plural(commits.size, "commit")
}
</td>
<td style="width: 25%; text-align: center;">
<i class="octicon octicon-diff"></i>
<strong>@diffs.size</strong> @plural(diffs.size, "file") changed
</td>
<td style="width: 25%; text-align: center;">
<i class="octicon octicon-comment"></i>
@defining(comments.collect { case c: gitbucket.core.model.CommitComment => c }){ comments =>
<strong>@comments.size</strong> commit @plural(comments.size, "comment")
}
</td>
<td style="width: 25%; text-align: center;">
<i class="octicon octicon-organization"></i>
@defining(commits.flatMap(_.map(_.authorEmailAddress)).distinct){ contributors =>
<strong>@contributors.size</strong> @plural(contributors.size, "contributor")
}
</td>
</tr>
</table>
</div>
<div class="box" style="margin-bottom: 20px;">
@commits.map { day =>
<div style="margin-top: 8px; margin-bottom: 8px;" class="muted">
Commits on @date(day.head.commitTime)
</div>
<table style="width: 100%;">
@day.map { commit =>
<tr>
<td style="width: 20%;">
<i class="octicon octicon-git-commit"></i>
@avatar(commit, 20)
@user(commit.authorName, commit.authorEmailAddress, "username strong")
</td>
<td><span class="monospace">@commit.shortMessage</span></td>
@*
<span class="badge" style="display: inline">@if(comments.isDefined){
@comments.get.flatMap @{
case comment: CommitComment => Some(comment)
case other => None
}.count(t => t.commitId == commit.id && !t.pullRequest)
}</span>
*@
<td style="width: 10%; text-align: right;">
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
</td>
</tr>
}
</table>
}
</div>
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true, None, false, false)
<p>Showing you all comments on commits in this comparison.</p>
@issues.html.commentlist(None, comments, hasWritePermission, repository, None)
@issues.html.commentlist(None, comments, false, repository, None)
}
}
}
<script>
$(function(){
$('#edit-compare-condition').click(function(){
$('#compare-info').hide();
$('#compare-edit').show();
});
$('#cancel-condition-editing').click(function(){
$('#compare-info').show();
$('#compare-edit').hide();
});
$('a.origin-owner, a.forked-owner, a.origin-branch, a.forked-branch').click(function(){
var e = $(this);
e.parents('ul').find('i').attr('class', 'icon-white');
e.find('i').attr('class', 'icon-ok');
e.find('i').attr('class', 'octicon-check');
e.parents('div.btn-group').find('button span.strong').text(e.text());
@if(members.isEmpty){
location.href = '@url(repository)/compare/' +
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'));
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
} else {
location.href = '@path/' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('owner')) + '/' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('name')) +'/compare/' +
$.trim($('i.icon-ok').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'));
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + '/' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('name')) +'/compare/' +
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
}
});
$('#show-form').click(function(){
$(this).hide();
$('#create-pull-request').hide();
$('#pull-request-form').show();
});
if(location.search.substr(1).split("&").indexOf("expand=1")!=-1){
$('#show-form').click();
}
@if(hasWritePermission){
function checkConflict(from, to, noConflictHandler, hasConflictHandler){
@if(loginAccount.isDefined){
function checkConflict(from, to){
$('.check-conflict').show();
$.get('@url(repository)/compare/' + from + '...' + to + '/mergecheck',
$.get('@url(forkedRepository)/compare/' + from + '...' + to + '/mergecheck',
function(data){ $('.check-conflict').html(data); });
}
@if(members.isEmpty){
checkConflict(
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')),
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'))
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
);
} else {
checkConflict(
$.trim($('i.icon-ok').parents('a.origin-owner' ).data('owner')) + ":" +
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')),
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('owner')) + ":" +
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'))
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ":" +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ":" +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
);
}
}

View File

@@ -45,7 +45,7 @@
}
</div>
<div class="span2">
@issues.html.issueinfo(issue, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div>
</div>
<script>

View File

@@ -1,9 +1,12 @@
@(hasConflict: Boolean)
@if(hasConflict){
<h4>We cant automatically merge these branches</h4>
<p>Don't worry, you can still submit the pull request.</p>
} else {
<h4 style="color: #468847;">Able to merge</h4>
<p>These branches can be automatically merged.</p>
}
<input type="submit" class="btn btn-success btn-block" value="Create pull request"/>
<div style="margin-top: 8px;">
@if(hasConflict){
<i class="octicon octicon-x" style="color: #bd2c00"></i>
<span class="strong" style="color: #bd2c00;">Cant automatically merge.</span>
<span class="muted">Dont worry, you can still create the pull request.</span>
} else {
<i class="octicon octicon-check" style="color: #6cc644"></i>
<span class="strong" style="color: #6cc644">Able to merge.</span>
<span class="muted">These branches can be automatically merged.</span>
}
</div>

View File

@@ -3,8 +3,9 @@
issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest,
statuses: List[model.CommitStatus],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
requestRepositoryUrl: String)(implicit context: gitbucket.core.controller.Context)
originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.service.SystemSettingsService
@import context._
@import gitbucket.core.view.helpers._
@import model.CommitState
@@ -22,100 +23,101 @@
@status.description.map{ desc => <span class="muted">— @desc</span> }
</div>
}
}else{
@defining(statuses.groupBy(_.state)){ stateMap => @defining(CommitState.combine(stateMap.keySet)){ state =>
<div class="build-status-item">
<a class="pull-right" id="toggle-all-checks"></a>
<span class="build-status-icon text-@{state.name}">@commitStateIcon(state)</span>
<strong class="text-@{state.name}">@commitStateText(state, pullreq.commitIdTo)</strong>
<span class="text-@{state.name}">— @{stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")} checks</span>
</div>
<div class="build-statuses-list" style="@if(state==CommitState.SUCCESS){ display:none; }else{ }">
@statuses.map{ status =>
} else {
@defining(statuses.groupBy(_.state)){ stateMap =>
@defining(CommitState.combine(stateMap.keySet)){ state =>
<div class="build-status-item">
@status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
<span class="text-@{status.state.name}">@status.context</span>
@status.description.map{ desc => <span class="muted">— @desc</span> }
<a class="pull-right" id="toggle-all-checks"></a>
<span class="build-status-icon text-@{state.name}">@commitStateIcon(state)</span>
<strong class="text-@{state.name}">@commitStateText(state, pullreq.commitIdTo)</strong>
<span class="text-@{state.name}">— @{stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")} checks</span>
</div>
}
</div>
} }
<div class="build-statuses-list" style="@if(state==CommitState.SUCCESS){ display:none; }else{ }">
@statuses.map{ status =>
<div class="build-status-item">
@status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
<span class="text-@{status.state.name}">@status.context</span>
@status.description.map{ desc => <span class="muted">— @desc</span> }
</div>
}
</div>
}
}
}
</div>
}
<div class="pull-right">
<input type="button" class="btn @if(!hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/>
</div>
<div>
@if(hasConflict){
<span class="strong">We cant automatically merge this pull request.</span>
} else{ @if(hasProblem){
<span class="strong">Merge with caution!</span>
} else {
<span class="strong">This pull request can be automatically merged.</span>
} }
</div>
<div class="small">
@if(hasConflict){
<a href="#" id="show-command-line">Use the command line</a> to resolve conflicts before continuing.
} else {
You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
}
</div>
<div id="command-line" style="display: none;">
<hr>
@if(hasConflict){
<span class="strong">Checkout via command line</span>
<p>
If you cannot merge a pull request automatically here, you have the option of checking
it out via command line to resolve conflicts and perform a manual merge.
</p>
} else {
<span class="strong">Merging via command line</span>
<p>
If you do not want to use the merge button or an automatic merge cannot be performed,
you can perform a manual merge on the command line.
</p>
}
@helper.html.copy("repository-url-copy", requestRepositoryUrl){
<input type="text" style="width: 500px;" value="@requestRepositoryUrl" id="repository-url" readonly>
}
<div>
<p>
<span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory
</p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){
<pre style="width: 500px; float: left;">@command</pre>
}
}
</div>
<div>
<p>
<span class="strong">Step 2:</span> Bring in @{pullreq.requestUserName}'s changes and test
</p>
@defining(s"git pull ${requestRepositoryUrl} ${pullreq.requestBranch}"){ command =>
@helper.html.copy("merge-command-copy-2", command){
<pre style="width: 500px; float: left;">@command</pre>
}
}
</div>
<div>
<p>
<span class="strong">Step 3:</span> Merge the changes and update the server
</p>
@defining(s"git checkout ${pullreq.branch}\ngit merge ${pullreq.requestUserName}-${pullreq.requestBranch}\ngit push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-3", command){
<pre style="width: 500px; float: left;">@command</pre>
}
}
</div>
</div>
<div class="pull-right">
<input type="button" class="btn @if(!hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/>
</div>
<div>
@if(hasConflict){
<span class="strong">We cant automatically merge this pull request.</span>
} else {
@if(hasProblem){
<span class="strong">Merge with caution!</span>
} else {
<span class="strong">This pull request can be automatically merged.</span>
}
}
</div>
<div class="small">
@if(hasConflict){
<a href="#" id="show-command-line">Use the command line</a> to resolve conflicts before continuing.
} else {
You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
}
</div>
<div id="command-line" style="display: none;">
<hr>
@if(hasConflict){
<span class="strong">Checkout via command line</span>
<p>
If you cannot merge a pull request automatically here, you have the option of checking
it out via command line to resolve conflicts and perform a manual merge.
</p>
} else {
<span class="strong">Merging via command line</span>
<p>
If you do not want to use the merge button or an automatic merge cannot be performed,
you can perform a manual merge on the command line.
</p>
}
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){
<div class="btn-group" data-toggle="buttons-radio">
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button>
@if(settings.ssh && loginAccount.isDefined){
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
}
</div>
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly>
}
<div>
<p>
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
</p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
}
}
</div>
<div>
<p>
<span class="strong">Step 2:</span> Merge the changes and update on the server.
</p>
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
s"git push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-2", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;">@command</pre>
}
}
</div>
</div>
</div>
<div id="confirm-merge-form" style="display: none;">
<form method="POST" action="@url(repository)/pull/@pullreq.issueId/merge">
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge">
<div class="strong">
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
</div>
@@ -149,5 +151,31 @@ $(function(){
$('#merge-pull-request').hide();
$('#confirm-merge-form').show();
});
@if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){
// Update URL box
$('#repository-url').val('@forkedRepository.httpUrl');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
// Update command guidance
$('#merge-command').text($('#merge-command').text().replace(
'@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)',
'@forkedRepository.httpUrl'
));
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
});
$('#repository-url-ssh').click(function(){
// Update URL box
$('#repository-url').val('@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
// Update command guidance
$('#merge-command').text($('#merge-command').text().replace(
'@forkedRepository.httpUrl',
'@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)'
));
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
});
}
});
</script>

View File

@@ -18,9 +18,9 @@
<div>
<div class="show-title pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
<a class="btn btn-small" href="#" id="edit">Edit</a>
<a class="btn" href="#" id="edit">Edit</a>
}
<a class="btn btn-small btn-success" href="@url(repository)/issues/new">New issue</a>
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
</div>
<div class="edit-title pull-right" style="display: none;">
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>

View File

@@ -11,7 +11,7 @@
@html.menu("code", repository){
<div class="head">
<div class="pull-right hide-if-blame"><div class="btn-group">
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="icon icon-th-list"></i></a>
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="octicon octicon-list-unordered"></i></a>
</div></div>
<div class="line-age-legend">
<span>Newer</span>
@@ -47,57 +47,50 @@
}
}
</div>
<table class="table table-bordered blobview">
<tr>
<th style="font-weight: normal;">
<div class="pull-left">
@avatar(latestCommit, 20)
@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
<span class="muted">@helper.html.datetimeago(latestCommit.commitTime)</span>
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
</div>
<div class="btn-group pull-right">
@if(hasWritePermission && content.viewType == "text" && repository.branchList.contains(branch)){
<a class="btn btn-mini" href="@url(repository)/edit/@encodeRefName(branch)/@pathList.mkString("/")">Edit</a>
}
<a class="btn btn-mini" href="?raw=true">Raw</a>
@if(content.viewType == "text"){
<a class="btn btn-mini blame-action" href="@url(repository)/blame/@latestCommit.id/@pathList.mkString("/")" data-url="@url(repository)/get-blame/@latestCommit.id/@pathList.mkString("/")" data-repository="@url(repository)">Blame</a>
}
<a class="btn btn-mini" href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")">History</a>
@if(hasWritePermission){
<a class="btn btn-mini btn-danger" href="@url(repository)/remove/@encodeRefName(branch)/@pathList.mkString("/")">Delete</a>
}
</div>
</th>
</tr>
<tr>
<td>
@if(content.viewType == "text"){
@defining(isRenderable(pathList.reverse.head)){ isRrenderable =>
@if(!isBlame && isRrenderable) {
<div class="box-content markdown-body" style="border: none; padding-left: 16px; padding-right: 16px;">
@renderMarkup(pathList, content.content.get, branch, repository, false, false)
</div>
} else {
<pre class="prettyprint linenums blob @if(!isRrenderable){ no-renderable } ">@content.content.get</pre>
}
}
}
@if(content.viewType == "image"){
<img src="?raw=true"/>
}
@if(content.viewType == "large" || content.viewType == "binary"){
<div style="text-align: center; padding-top: 20px; padding-bottom: 20px;">
<a href="?raw=true">View Raw</a><br>
<br>
(Sorry about that, but we can't show files that are this big right now)
<div class="box-header">
@avatar(latestCommit, 20)
@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
<span class="muted">@helper.html.datetimeago(latestCommit.commitTime)</span>
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
<div class="btn-group pull-right">
@if(hasWritePermission && content.viewType == "text" && repository.branchList.contains(branch)){
<a class="btn btn-mini" href="@url(repository)/edit/@encodeRefName(branch)/@pathList.mkString("/")">Edit</a>
}
<a class="btn btn-mini" href="?raw=true">Raw</a>
@if(content.viewType == "text"){
<a class="btn btn-mini blame-action" href="@url(repository)/blame/@latestCommit.id/@pathList.mkString("/")" data-url="@url(repository)/get-blame/@latestCommit.id/@pathList.mkString("/")" data-repository="@url(repository)">Blame</a>
}
<a class="btn btn-mini" href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")">History</a>
@if(hasWritePermission && repository.branchList.contains(branch)){
<a class="btn btn-mini btn-danger" href="@url(repository)/remove/@encodeRefName(branch)/@pathList.mkString("/")">Delete</a>
}
</div>
</div>
@if(content.viewType == "text"){
@defining(isRenderable(pathList.reverse.head)){ isRrenderable =>
@if(!isBlame && isRrenderable) {
<div class="box-content-bottom markdown-body" style="padding-left: 16px; padding-right: 16px;">
@renderMarkup(pathList, content.content.get, branch, repository, false, false, true)
</div>
} else {
<div class="box-content-bottom">
<pre class="prettyprint linenums blob @if(!isRrenderable){ no-renderable } ">@content.content.get</pre>
</div>
}
</td>
</tr>
</table>
}
}
@if(content.viewType == "image"){
<div class="box-content-bottom">
<img src="?raw=true"/>
</div>
}
@if(content.viewType == "large" || content.viewType == "binary"){
<div class="box-content-bottom" style="text-align: center; padding-top: 20px; padding-bottom: 20px;">
<a href="?raw=true">View Raw</a><br>
<br>
(Sorry about that, but we can't show files that are this big right now)
</div>
}
}
}
<script src="@assets/vendors/jquery/jquery.ba-hashchange.js"></script>
@@ -111,36 +104,38 @@ $(window).load(function(){
function updateSourceLineNum(){
$('.source-line-num').remove();
var pos = pre.find('ol.linenums').position();
$('<div class="source-line-num">').css({
height:pre.height(),
width:'48px',
cursor:'pointer',
position: 'absolute',
top : pos.top + 'px',
left : pos.left + 'px'
}).click(function(e){
$(window).hashchange(function(){})
var pos = $(this).data("pos");
if(!pos){
pos = $('ol.linenums li').map(function(){ return {id:$(this).attr("id"),top:$(this).position().top} }).toArray();
$(this).data("pos",pos);
}
for(var i=0;i<pos.length-1;i++){
if(pos[i+1].top>e.pageY){
break;
if(pos){
$('<div class="source-line-num">').css({
height : pre.height(),
width : '48px',
cursor : 'pointer',
position: 'absolute',
top : pos.top + 'px',
left : pos.left + 'px'
}).click(function(e){
$(window).hashchange(function(){})
var pos = $(this).data("pos");
if(!pos){
pos = $('ol.linenums li').map(function(){ return { id: $(this).attr("id"), top: $(this).position().top} }).toArray();
$(this).data("pos",pos);
}
}
var line = pos[i].id.replace(/^L/,'');
var hash = location.hash;
if(e.shiftKey == true && hash.match(/#L\d+(-L\d+)?/)){
var lines = hash.split('-');
location.hash = lines[0] + '-L' + line;
} else {
var p = $("#L"+line).attr('id',"");
location.hash = '#L' + line;
p.attr('id','L'+line);
}
}).appendTo(pre);
for(var i = 0; i < pos.length-1; i++){
if(pos[i + 1].top > e.pageY){
break;
}
}
var line = pos[i].id.replace(/^L/,'');
var hash = location.hash;
if(e.shiftKey == true && hash.match(/#L\d+(-L\d+)?/)){
var lines = hash.split('-');
location.hash = lines[0] + '-L' + line;
} else {
var p = $("#L"+line).attr('id',"");
location.hash = '#L' + line;
p.attr('id','L'+line);
}
}).appendTo(pre);
}
}
var repository = $('.blame-action').data('repository');
$('.blame-action').click(function(e){
@@ -152,11 +147,11 @@ $(window).load(function(){
});
function updateBlame(){
var m = /^\/(blame|blob)(\/.*)$/.exec(location.href.substring(repository.length));
var m = /\/(blame|blob)(\/.*)$/.exec(location.href);
var mode = m[1];
$('.blame-action').toggleClass("active", mode=='blame').attr('href', repository + (m[1]=='blame'?'/blob':'/blame')+m[2]);
if(pre.parents("td").find(".blame").length){
pre.parents("div.container").toggleClass("blame-container", mode=='blame');
$('.blame-action').toggleClass("active", mode=='blame').attr('href', repository + (m[1] == 'blame' ? '/blob' : '/blame') + m[2]);
if(pre.parents("div.box-content-bottom").find(".blame").length){
pre.parents("div.container").toggleClass("blame-container", mode == 'blame');
updateSourceLineNum();
return;
}
@@ -164,47 +159,47 @@ $(window).load(function(){
updateSourceLineNum();
return;
}
$(document.body).toggleClass('no-box-shadow',document.body.style.boxShadow===undefined);
$(document.body).toggleClass('no-box-shadow', document.body.style.boxShadow===undefined);
$('.blame-action').addClass("active");
var base = $('<div class="blame">').css({height:pre.height()}).prependTo(pre.parents("td")[0]);
var base = $('<div class="blame">').css({height: pre.height()}).prependTo(pre.parents("div.box-content-bottom"));
base.parents("div.container").addClass("blame-container");
updateSourceLineNum();
$.get($('.blame-action').data('url')).done(function(data){
var blame = data.blame;
var index = [];
for(var i=0;i<blame.length;i++){
for(var j=0;j<blame[i].lines.length;j++){
index[blame[i].lines[j]]=blame[i];
for(var i = 0; i < blame.length; i++){
for(var j = 0; j < blame[i].lines.length; j++){
index[blame[i].lines[j]] = blame[i];
}
}
var blame, lastDiv, now=new Date().getTime();
var blame, lastDiv, now = new Date().getTime();
$('pre.prettyprint ol.linenums li').each(function(i, e){
var p=$(e).position();
var h=$(e).height();
var p = $(e).position();
var h = $(e).height();
if(blame == index[i]){
lastDiv.css("min-height",(p.top+h+1) - lastDiv.position().top);
lastDiv.css("min-height",(p.top + h + 1) - lastDiv.position().top);
}else{
$(e).addClass('blame-sep')
blame = index[i];
var sha = $('<div class="blame-sha">')
.append($('<a>').attr("href",data.root+'/commit/'+blame.id).text(blame.id.substr(0,7)));
.append($('<a>').attr("href", data.root + '/commit/' + blame.id).text(blame.id.substr(0,7)));
if(blame.prev){
sha.append($('<br />'))
.append($('<a class="muted-link">').text('prev').attr("href",data.root+'/blame/'+blame.prev+'/'+(blame.prevPath||data.path)));
.append($('<a class="muted-link">').text('prev').attr("href", data.root + '/blame/' + blame.prev + '/' + (blame.prevPath || data.path)));
}
lastDiv = $('<div class="blame-info">')
.addClass('heat'+Math.min(10,Math.max(1,Math.ceil((now-blame.commited)/(24*3600*1000*70)))))
.toggleClass('blame-last',blame.id==data.last)
.addClass('heat' + Math.min(10, Math.max(1, Math.ceil((now - blame.commited) / (24 * 3600 * 1000 * 70)))))
.toggleClass('blame-last', blame.id == data.last)
.data('line', (i + 1))
.css({
"top" : p.top + 'px',
"min-height" : h+'px'
"top" : p.top + 'px',
"min-height" : h + 'px'
})
.append(sha)
.append($(blame.avatar).addClass('avatar').css({"float":"left"}))
.append($(blame.avatar).addClass('avatar').css({"float": "left"}))
.append($('<div class="blame-commit-title">').text(blame.message))
.append($('<div class="muted">').html(blame.author+ " authed "+blame.authed))
.append($('<div class="muted">').html(blame.author + " authed " + blame.authed))
.appendTo(base);
}
});
@@ -214,6 +209,8 @@ $(window).load(function(){
updateBlame();
});
var scrolling = false;
/**
* Hightlight lines which are specified by URL hash.
*/
@@ -224,7 +221,7 @@ function updateHighlighting(){
var lines = hash.substr(1).split('-');
if(lines.length == 1){
$('#' + lines[0]).addClass('highlight');
if(!updateHighlighting.scrolling){
if(!scrolling){
$(window).scrollTop($('#' + lines[0]).offset().top - 40);
}
} else if(lines.length > 1){
@@ -233,11 +230,11 @@ function updateHighlighting(){
for(var i = start; i <= end; i++){
$('#L' + i).addClass('highlight');
}
if(!updateHighlighting.scrolling){
if(!scrolling){
$(window).scrollTop($('#L' + start).offset().top - 40);
}
}
updateHighlighting.scrolling = true;
scrolling = true;
}
}
</script>

View File

@@ -30,16 +30,24 @@
}
}.getOrElse{
@if(context.loginAccount.isDefined){
<a href="@url(repository)/compare/@{encodeRefName(repository.repository.defaultBranch)}...@{encodeRefName(branch.name)}?expand=1" class="btn btn-small">New Pull Request</a>
}else{
<a href="@url(repository)/compare/@{encodeRefName(repository.repository.defaultBranch)}...@{encodeRefName(branch.name)}" class="btn btn-small">Compare</a>
<a href="@url(repository)/compare/@{repository.repository.parentUserName.map { parent =>
urlEncode(parent) + ":" + encodeRefName(repository.repository.defaultBranch)
}.getOrElse {
encodeRefName(repository.repository.defaultBranch)
}}...@{encodeRefName(branch.name)}?expand=1" class="btn btn-small">New Pull Request</a>
} else {
<a href="@url(repository)/compare/@{repository.repository.parentUserName.map { parent =>
urlEncode(parent) + ":" + encodeRefName(repository.repository.defaultBranch)
}.getOrElse {
encodeRefName(repository.repository.defaultBranch)
}}...@{encodeRefName(branch.name)}" class="btn btn-small">Compare</a>
}
}
@if(hasWritePermission){
@if(prs.map(!_._2.closed).getOrElse(false)){
<a class="btn disabled btn-mini" data-toggle="tooltip" title="You cant delete this branch because it has an open pull request"><i class="icon icon-trash icon-white"></i></a>
<a class="btn disabled btn-mini" data-toggle="tooltip" title="You cant delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a>
}else{
<a href="@url(repository)/delete/@encodeRefName(branch.name)" class="btn @if(info.isMerged){ btn-warning }else{ btn-danger } delete-branch btn-mini" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="icon icon-trash icon-white"></i></a>
<a href="@url(repository)/delete/@encodeRefName(branch.name)" class="btn @if(info.isMerged){ btn-warning }else{ btn-danger } delete-branch btn-mini" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan"></i></a>
}
}
}
@@ -75,7 +83,7 @@
<script>
$(function(){
$('.delete-branch').click(function(e){
var branchName = $(e.target).data('name');
var branchName = $(e.target).closest('a').data('name');
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
});
$('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap");

View File

@@ -15,7 +15,16 @@
}
<div class="box issue-comment-box">
<div class="box-content">
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 635px; height: 100px; max-height: 150px;", elastic = true)
@helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
enableRefsLink = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
style = "width: 635px; height: 100px; max-height: 150px;",
elastic = true
)
</div>
@if(fileName.isDefined){
<div class="pull-right" style="margin-top: 10px;">

View File

@@ -14,7 +14,7 @@
@html.menu("code", repository){
<table class="table table-bordered">
<tr>
<th>
<th class="box-header">
<div class="pull-right align-right">
<a href="@url(repository)/tree/@commit.id" class="btn btn-small">Browse code</a>
</div>
@@ -25,7 +25,7 @@
<div class="small" style="font-weight: normal;">
@if(branches.nonEmpty){
<span class="muted">
<img src="@assets/common/images/branch.png"/>
<i class="octicon octicon-git-branch"></i>
@branches.zipWithIndex.map { case (branch, i) =>
<a href="@url(repository)/tree/@encodeRefName(branch)" class="branch" id="branch-@i">@branch</a>
}
@@ -33,7 +33,7 @@
}
@if(tags.nonEmpty){
<span class="muted">
<img src="@assets/common/images/tag.png"/>
<i class="octicon octicon-tag"></i>
@tags.zipWithIndex.map { case (tag, i) =>
<a href="@url(repository)/tree/@tag" class="tag" id="tag-@i">@tag</a>
}
@@ -74,7 +74,7 @@
</div>
@if(commit.isDifferentFromAuthor) {
<div class="committer">
<span class="icon-arrow-right"></span>
<span class="octicon octicon-arrow-right"></span>
<span>@user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
<span class="muted"> committed @helper.html.datetimeago(commit.commitTime)</span>
</div>

View File

@@ -10,72 +10,81 @@
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){
<div class="head">
@helper.html.branchcontrol(
branch,
repository,
hasWritePermission
){
@repository.branchList.map { x =>
<li><a href="@url(repository)/commits/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
}
}
@if(pathList.isEmpty){
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / Commit History
@helper.html.branchcontrol(
branch,
repository,
hasWritePermission
){
@repository.branchList.map { x =>
<li><a href="@url(repository)/commits/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
}
}
}
@if(pathList.nonEmpty){
<span class="muted">History for</span>
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
<a class="strong" href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
@pathList.zipWithIndex.map { case (section, i) =>
@if(i == pathList.length - 1){
@section
<span class="strong">@section</span>
} else {
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
<a class="strong" href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
}
}
}
</div>
@commits.map { day =>
<table class="table table-bordered">
<tr>
<th>@date(day.head.commitTime)</th>
</tr>
@day.map { commit =>
<tr>
<td>
<div class="pull-right align-right">
<a href="@url(repository)/commit/@commit.id" class="btn btn-small monospace">@commit.id.substring(0, 10)</a><br>
<a href="@url(repository)/tree/@commit.id" class="small">Browse code</a>
</div>
<div>
<div class="commit-avatar-image">@avatar(commit, 40)</div>
<div class="commit-message-box">
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
}
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div class="small">
@user(commit.authorName, commit.authorEmailAddress, "username")
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
@if(commit.isDifferentFromAuthor) {
<span class="icon-arrow-right" style="margin-top : -2px ;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
<div class="commit-list">
@commits.map { day =>
<div class="muted" style="background-color: white;">
<i class="octicon octicon-git-commit"></i> Commits on @date(day.head.commitTime)
</div>
<div class="box-commits">
@day.map { commit =>
<div class="box-content-row" style="padding: 8px;">
<ul class="nav nav-pills-group pull-right" style="margin-top: 2px; margin-bottom: 0px; margin-right: 4px;">
<li class="first"><a href="@url(repository)/commit/@commit.id" class="link monospace">@commit.id.substring(0, 7)</a></li>
<li class="last"><a href="@url(repository)/tree/@commit.id" style="padding-top: 9px; padding-bottom: 10px;"><i class="octicon octicon-code link"></i></a></li>
</ul>
<div>
<div class="commit-avatar-image">@avatar(commit, 40)</div>
<div class="commit-message-box">
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
}
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div class="small">
@user(commit.authorName, commit.authorEmailAddress, "username")
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
@if(commit.isDifferentFromAuthor) {
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
}
</div>
</div>
</div>
</div>
</td>
</tr>
}
</div>
}
</div>
<div class="pagination" style="text-align: center; margin-top: 30px">
<ul>
@if(page <= 1){
<li class="disabled"><span>Newer</span></li>
} else {
<li><a href="?page=@{page - 1}">Newer</a></li>
}
</table>
}
<div class="btn-group">
<button class="btn" onclick="location.href='?page=@{page - 1}'"@if(page <= 1){ disabled="true"}>&lt; Newer</button>
<button class="btn" onclick="location.href='?page=@{page + 1}'"@if(!hasNext){ disabled="true"}>Older &gt;</button>
@if(!hasNext){
<li class="disabled"><span>Older</span></li>
} else {
<li><a href="?page=@{page + 1}">Older</a></li>
}
</ul>
</div>
}
}

View File

@@ -15,15 +15,15 @@
@html.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){
<div class="head">
<div class="pull-right"><div class="btn-group">
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="icon icon-th-list"></i></a>
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-small" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="octicon octicon-list-unordered"></i></a>
@if(pathList.nonEmpty){
<a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" title="Browse commits for this branch"><i class="icon icon-time"></i></a>
<a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-small" data-toggle="tooltip" data-placement="bottom" title="Browse commits for this branch"><i class="octicon octicon-clock"></i></a>
}
</div></div>
@branchPullRequest.map{ case (pullRequest, issue) =>
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-pullrequest-branch btn-mini" title="@issue.title" data-toggle="tooltip">#@pullRequest.issueId</a>
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-small btn-pullrequest-branch" title="@issue.title" data-toggle="tooltip">#@pullRequest.issueId</a>
}.getOrElse{
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-success btn-mini"><i class="icon-white icon-retweet" data-toggle="tooltip" title="Compare, review, create a pull request"></i></a>
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-small btn-success"><i class="octicon octicon-git-compare" data-toggle="tooltip" title="Compare, review, create a pull request"></i></a>
}
@helper.html.branchcontrol(
branch,
@@ -39,12 +39,12 @@
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
}
@if(hasWritePermission){
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")"><img src="@assets/common/images/newfile.png"/></a>
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")" title="Create a new file here" style="text-decoration: none;">+</i></a>
}
</div>
<table class="table table-file-list">
<tr>
<th colspan="4" style="font-weight: normal;">
<th colspan="4" style="font-weight: normal; border: none;">
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
@if(latestCommit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@latestCommit.id').toggle();" class="omit">...</a>
@@ -66,7 +66,7 @@
</div>
@if(latestCommit.isDifferentFromAuthor) {
<div class="committer">
<span class="icon-arrow-right"></span>
<span class="octicon octicon-arrow-right"></span>
<span>@user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
<span class="muted"> committed @helper.html.datetimeago(latestCommit.commitTime)</span>
</div>
@@ -77,7 +77,7 @@
</tr>
@if(pathList.size > 0){
<tr>
<td width="16"></td>
<td width="16" class="file-icon"></td>
<td><a href="@url(repository)@if(pathList.size > 1){/tree/@encodeRefName(branch)/@pathList.init.mkString("/")}">..</a></td>
<td></td>
<td></td>
@@ -85,18 +85,18 @@
}
@files.map { file =>
<tr>
<td width="16">
<td width="16" class="file-icon">
@if(file.isDirectory){
@if(file.linkUrl.isDefined){
<img src="@assets/common/images/folder_link.png"/>
<i class="octicon octicon-file-symlink-directory"></i>
} else {
<img src="@assets/common/images/folder.png"/>
<i class="octicon octicon-file-directory"></i>
}
} else {
<img src="@assets/common/images/file.png"/>
<i class="octicon octicon-file-text"></i>
}
</td>
<td>
<td class="content-name">
@if(file.isDirectory){
@if(file.linkUrl.isDefined){
<a href="@file.linkUrl">
@@ -118,8 +118,7 @@
}
</td>
<td class="mute">
<a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a>
[@user(file.author, file.mailAddress)]
<a href="@url(repository)/commit/@file.commitId" class="commit-message shorten-text" title="@file.message">@link(file.message, repository)</a>
</td>
<td style="text-align: right;">@helper.html.datetimeago(file.time, false)</td>
</tr>
@@ -128,7 +127,7 @@
@readme.map { case(filePath, content) =>
<div id="readme">
<div class="box-header">@filePath.reverse.head</div>
<div class="box-content markdown-body">@renderMarkup(filePath, content, branch, repository, false, false)</div>
<div class="box-content-bottom markdown-body" style="padding-left: 16px; padding-right: 16px;">@renderMarkup(filePath, content, branch, repository, false, false, true)</div>
</div>
}
}

Some files were not shown because too many files have changed in this diff Show More