Sync upstream/maste to master and Merge branch 'master' into add-features-to-ldapauth
Conflicts: src/main/scala/app/IndexController.scala src/main/scala/service/SystemSettingsService.scala src/main/twirl/admin/system.scala.html
17
README.md
@@ -23,7 +23,7 @@ Following features are not implemented, but we will make them in the future rele
|
|||||||
- File editing in repository viewer
|
- File editing in repository viewer
|
||||||
- Comment for the changeset
|
- Comment for the changeset
|
||||||
- Network graph
|
- Network graph
|
||||||
- Statics
|
- Statistics
|
||||||
- Watch / Star
|
- Watch / Star
|
||||||
|
|
||||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
|
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
|
||||||
@@ -42,7 +42,6 @@ or you can start GitBucket by `java -jar gitbucket.war` without servlet containe
|
|||||||
- --port=[NUMBER]
|
- --port=[NUMBER]
|
||||||
- --prefix=[CONTEXTPATH]
|
- --prefix=[CONTEXTPATH]
|
||||||
- --host=[HOSTNAME]
|
- --host=[HOSTNAME]
|
||||||
- --https=true
|
|
||||||
- --gitbucket.home=[DATA_DIR]
|
- --gitbucket.home=[DATA_DIR]
|
||||||
|
|
||||||
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||||
@@ -59,12 +58,24 @@ Run the following commands in `Terminal` to
|
|||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
--------
|
||||||
|
### 1.11 - 01 Mar 2014
|
||||||
|
- Base URL for redirection, notification and repository URL box is configurable
|
||||||
|
- Remove ```--https``` option because it's possible to substitute in the base url
|
||||||
|
- Headline anchor is available for Markdown contents such as Wiki page
|
||||||
|
- Improve H2 connectivity
|
||||||
|
- Label is available for pull requests not only issues
|
||||||
|
- Delete branch button is added
|
||||||
|
- Repository icons are updated
|
||||||
|
- Select lines of source code by URL hash like `#L10` or `#L10-L15` in repository viewer
|
||||||
|
- Display reference to issue from others in comment list
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
### 1.10 - 01 Feb 2014
|
### 1.10 - 01 Feb 2014
|
||||||
- Rename repository
|
- Rename repository
|
||||||
- Transfer repository owner
|
- Transfer repository owner
|
||||||
- Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
|
- Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
|
||||||
- Add LDAP display name attribute
|
- Add LDAP display name attribute
|
||||||
- Response improvement
|
- Response performance improvement
|
||||||
- Fix some bugs
|
- Fix some bugs
|
||||||
|
|
||||||
### 1.9 - 28 Dec 2013
|
### 1.9 - 28 Dec 2013
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
# Server port
|
# Server port
|
||||||
#GITBUCKET_PORT=8080
|
#GITBUCKET_PORT=8080
|
||||||
|
|
||||||
# Force HTTPS scheme
|
|
||||||
#GITBUCKET_HTTPS=false
|
|
||||||
|
|
||||||
# Data directory (GITBUCKET_HOME/gitbucket)
|
# Data directory (GITBUCKET_HOME/gitbucket)
|
||||||
#GITBUCKET_HOME=/var/lib/gitbucket
|
#GITBUCKET_HOME=/var/lib/gitbucket
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ start() {
|
|||||||
if [ $GITBUCKET_HOST ]; then
|
if [ $GITBUCKET_HOST ]; then
|
||||||
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
|
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
|
||||||
fi
|
fi
|
||||||
if [ $GITBUCKET_HTTPS ]; then
|
|
||||||
START_OPTS="${START_OPTS} --https=true"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run the Java process
|
# Run the Java process
|
||||||
GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
|
GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
|
||||||
|
|||||||
242
etc/icons.svg
@@ -25,17 +25,17 @@
|
|||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="1.4"
|
inkscape:zoom="1.4"
|
||||||
inkscape:cx="629.30023"
|
inkscape:cx="450.21999"
|
||||||
inkscape:cy="281.44758"
|
inkscape:cy="97.51519"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1-9"
|
inkscape:current-layer="layer1-9"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
inkscape:window-width="1366"
|
inkscape:window-width="1366"
|
||||||
inkscape:window-height="705"
|
inkscape:window-height="706"
|
||||||
inkscape:window-x="-8"
|
inkscape:window-x="1912"
|
||||||
inkscape:window-y="-8"
|
inkscape:window-y="-8"
|
||||||
inkscape:window-maximized="1"
|
inkscape:window-maximized="1"
|
||||||
inkscape:snap-global="true"
|
inkscape:snap-global="false"
|
||||||
inkscape:snap-grids="false"
|
inkscape:snap-grids="false"
|
||||||
inkscape:snap-page="false"
|
inkscape:snap-page="false"
|
||||||
inkscape:snap-bbox="true"
|
inkscape:snap-bbox="true"
|
||||||
@@ -746,6 +746,238 @@
|
|||||||
d="m 937.41093,1044.4944 0,30.6797 -28.50183,0 0,41.2377 28.50183,0 0,27.1288 41.19033,0 0,-27.1288 29.35404,0 0,-41.2377 -29.35404,0 0,-30.6797 -41.19033,0 z"
|
d="m 937.41093,1044.4944 0,30.6797 -28.50183,0 0,41.2377 28.50183,0 0,27.1288 41.19033,0 0,-27.1288 29.35404,0 0,-41.2377 -29.35404,0 0,-30.6797 -41.19033,0 z"
|
||||||
id="rect2995-0-2-7-7"
|
id="rect2995-0-2-7-7"
|
||||||
inkscape:connector-curvature="0" />
|
inkscape:connector-curvature="0" />
|
||||||
|
<rect
|
||||||
|
style="color:#000000;fill:none;stroke:#b3b3b3;stroke-width:9.34194565;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="rect3083"
|
||||||
|
width="170.93134"
|
||||||
|
height="207.72536"
|
||||||
|
x="38.526306"
|
||||||
|
y="1299.8645" />
|
||||||
|
<rect
|
||||||
|
style="color:#000000;fill:none;stroke:#b3b3b3;stroke-width:8.41239071;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="rect3083-7"
|
||||||
|
width="171.86089"
|
||||||
|
height="167.53221"
|
||||||
|
x="38.061527"
|
||||||
|
y="1300.4821" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4"
|
||||||
|
y="1301.3412"
|
||||||
|
x="42.553577"
|
||||||
|
height="163.64935"
|
||||||
|
width="29.769083"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-0"
|
||||||
|
y="1321.9025"
|
||||||
|
x="85.732407"
|
||||||
|
height="17.555511"
|
||||||
|
width="16.782965"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-0-9"
|
||||||
|
y="1356.7848"
|
||||||
|
x="85.732407"
|
||||||
|
height="17.555511"
|
||||||
|
width="16.782965"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-0-9-4"
|
||||||
|
y="1391.6671"
|
||||||
|
x="85.732407"
|
||||||
|
height="17.555511"
|
||||||
|
width="16.782965"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-0-9-4-8"
|
||||||
|
y="1426.5494"
|
||||||
|
x="85.732407"
|
||||||
|
height="17.555511"
|
||||||
|
width="16.782965"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-8"
|
||||||
|
y="1482.7141"
|
||||||
|
x="70.149086"
|
||||||
|
height="30.541632"
|
||||||
|
width="42.755199"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="color:#000000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:7.55999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="path4002"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="235.71429"
|
||||||
|
sodipodi:cy="1000.2193"
|
||||||
|
sodipodi:r1="15.016997"
|
||||||
|
sodipodi:r2="7.5084987"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 250.73129,1000.2193 -11.26275,6.5026 -11.26274,6.5025 0,-13.0051 0,-13.0051 11.26274,6.50255 z"
|
||||||
|
transform="matrix(1.0346242,0,0,1.5150471,-165.95814,-2.7851671)"
|
||||||
|
inkscape:transform-center-x="-2.5637799" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="color:#000000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:7.55999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="path4002-2"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="235.71429"
|
||||||
|
sodipodi:cy="1000.2193"
|
||||||
|
sodipodi:r1="15.016997"
|
||||||
|
sodipodi:r2="7.5084987"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 250.73129,1000.2193 -11.26275,6.5026 -11.26274,6.5025 0,-13.0051 0,-13.0051 11.26274,6.50255 z"
|
||||||
|
transform="matrix(-0.93510984,0,0,1.5150471,326.24502,-2.7851671)"
|
||||||
|
inkscape:transform-center-x="3.5106467" />
|
||||||
|
<rect
|
||||||
|
style="color:#000000;fill:none;stroke:#b3b3b3;stroke-width:9.34194565;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="rect3083-4"
|
||||||
|
width="170.93134"
|
||||||
|
height="207.72536"
|
||||||
|
x="280.50113"
|
||||||
|
y="1299.152" />
|
||||||
|
<rect
|
||||||
|
style="color:#000000;fill:none;stroke:#b3b3b3;stroke-width:8.41239071;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="rect3083-7-5"
|
||||||
|
width="171.86087"
|
||||||
|
height="167.53221"
|
||||||
|
x="280.03638"
|
||||||
|
y="1299.7695" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5"
|
||||||
|
y="1300.6287"
|
||||||
|
x="284.52841"
|
||||||
|
height="163.64934"
|
||||||
|
width="29.769083"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-8-5"
|
||||||
|
y="1482.0016"
|
||||||
|
x="312.12393"
|
||||||
|
height="30.541632"
|
||||||
|
width="42.755199"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="color:#000000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:7.55999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="path4002-27"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="235.71429"
|
||||||
|
sodipodi:cy="1000.2193"
|
||||||
|
sodipodi:r1="15.016997"
|
||||||
|
sodipodi:r2="7.5084987"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 250.73129,1000.2193 -11.26275,6.5026 -11.26274,6.5025 0,-13.0051 0,-13.0051 11.26274,6.50255 z"
|
||||||
|
transform="matrix(1.0346242,0,0,1.5150471,76.016678,-3.496726)"
|
||||||
|
inkscape:transform-center-x="-3.8842459"
|
||||||
|
inkscape:transform-center-y="-1.5464308e-005" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="color:#000000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:7.55999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||||
|
id="path4002-2-6"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="235.71429"
|
||||||
|
sodipodi:cy="1000.2193"
|
||||||
|
sodipodi:r1="15.016997"
|
||||||
|
sodipodi:r2="7.5084987"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 250.73129,1000.2193 -11.26275,6.5026 -11.26274,6.5025 0,-13.0051 0,-13.0051 11.26274,6.50255 z"
|
||||||
|
transform="matrix(-0.93510984,0,0,1.5150471,568.21986,-3.496726)"
|
||||||
|
inkscape:transform-center-x="5.318797"
|
||||||
|
inkscape:transform-center-y="-1.5464308e-005" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-7"
|
||||||
|
y="1392.2405"
|
||||||
|
x="365.67133"
|
||||||
|
height="58.049755"
|
||||||
|
width="29.769083"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-7-6"
|
||||||
|
y="1319.5453"
|
||||||
|
x="326.67615"
|
||||||
|
height="49.632401"
|
||||||
|
width="29.769083"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-7-8"
|
||||||
|
y="1179.0293"
|
||||||
|
x="-767.54126"
|
||||||
|
height="58.049755"
|
||||||
|
width="29.769083"
|
||||||
|
style="fill:#b3b3b3;stroke:none"
|
||||||
|
transform="matrix(0.68860063,-0.7251408,0.7251408,0.68860063,0,0)" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-7-6-9"
|
||||||
|
y="1319.5453"
|
||||||
|
x="403.28595"
|
||||||
|
height="49.632404"
|
||||||
|
width="29.769083"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-7-8-2"
|
||||||
|
y="623.14606"
|
||||||
|
x="-1287.8975"
|
||||||
|
height="55.681484"
|
||||||
|
width="28.564859"
|
||||||
|
style="fill:#b3b3b3;stroke:none"
|
||||||
|
transform="matrix(-0.68607628,-0.72752961,-0.72274236,0.69111755,0,0)" />
|
||||||
|
<rect
|
||||||
|
style="color:#000000;fill:#ffffff;stroke:#b3b3b3;stroke-width:7.29121827999999980;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;fill-opacity:1"
|
||||||
|
id="rect3083-7-5-7"
|
||||||
|
width="172.98204"
|
||||||
|
height="125.03616"
|
||||||
|
x="529.78156"
|
||||||
|
y="1383.6165" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-9"
|
||||||
|
y="1385.3533"
|
||||||
|
x="663.37042"
|
||||||
|
height="123.85819"
|
||||||
|
width="38.18644"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-9-5"
|
||||||
|
y="1401.4539"
|
||||||
|
x="552.03174"
|
||||||
|
height="15.96297"
|
||||||
|
width="117.00352"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-9-5-4"
|
||||||
|
y="1437.4023"
|
||||||
|
x="551.16083"
|
||||||
|
height="15.96297"
|
||||||
|
width="117.00352"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<rect
|
||||||
|
id="rect2995-0-4-5-9-5-4-3"
|
||||||
|
y="1473.7642"
|
||||||
|
x="551.16083"
|
||||||
|
height="15.96297"
|
||||||
|
width="117.00352"
|
||||||
|
style="fill:#b3b3b3;stroke:none" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#b3b3b3;stroke-width:23.0681076;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 558.62308,1380.7989 0,-45.237 c 0,0 13.52904,-35.6384 56.38304,-36.1894 40.81922,-0.5248 55.47363,34.6931 55.47363,34.6931 l 0.17276,48.4719"
|
||||||
|
id="path4310"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccscc" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 47 KiB |
@@ -25,8 +25,6 @@ public class JettyLauncher {
|
|||||||
port = Integer.parseInt(dim[1]);
|
port = Integer.parseInt(dim[1]);
|
||||||
} else if(dim[0].equals("--prefix")) {
|
} else if(dim[0].equals("--prefix")) {
|
||||||
contextPath = dim[1];
|
contextPath = dim[1];
|
||||||
} else if(dim[0].equals("--https") && (dim[1].equals("1") || dim[1].equals("true"))) {
|
|
||||||
forceHttps = true;
|
|
||||||
} else if(dim[0].equals("--gitbucket.home")){
|
} else if(dim[0].equals("--gitbucket.home")){
|
||||||
System.setProperty("gitbucket.home", dim[1]);
|
System.setProperty("gitbucket.home", dim[1]);
|
||||||
}
|
}
|
||||||
@@ -36,7 +34,7 @@ public class JettyLauncher {
|
|||||||
|
|
||||||
Server server = new Server();
|
Server server = new Server();
|
||||||
|
|
||||||
HttpsSupportConnector connector = new HttpsSupportConnector(forceHttps);
|
SelectChannelConnector connector = new SelectChannelConnector();
|
||||||
if(host != null) {
|
if(host != null) {
|
||||||
connector.setHost(host);
|
connector.setHost(host);
|
||||||
}
|
}
|
||||||
@@ -62,19 +60,3 @@ public class JettyLauncher {
|
|||||||
server.join();
|
server.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HttpsSupportConnector extends SelectChannelConnector {
|
|
||||||
private boolean forceHttps;
|
|
||||||
|
|
||||||
public HttpsSupportConnector(boolean forceHttps) {
|
|
||||||
this.forceHttps = forceHttps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void customize(final EndPoint endpoint, final Request request) throws IOException {
|
|
||||||
if (this.forceHttps) {
|
|
||||||
request.setScheme("https");
|
|
||||||
super.customize(endpoint, request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,16 +5,13 @@ import util.{FileUtil, OneselfAuthenticator}
|
|||||||
import util.StringUtil._
|
import util.StringUtil._
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.scalatra.FlashMapSupport
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
class AccountController extends AccountControllerBase
|
class AccountController extends AccountControllerBase
|
||||||
with SystemSettingsService with AccountService with RepositoryService with ActivityService
|
with AccountService with RepositoryService with ActivityService with OneselfAuthenticator
|
||||||
with OneselfAuthenticator
|
|
||||||
|
|
||||||
trait AccountControllerBase extends AccountManagementControllerBase with FlashMapSupport {
|
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||||
self: SystemSettingsService with AccountService with RepositoryService with ActivityService
|
self: AccountService with RepositoryService with ActivityService with OneselfAuthenticator =>
|
||||||
with OneselfAuthenticator =>
|
|
||||||
|
|
||||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||||
url: Option[String], fileId: Option[String])
|
url: Option[String], fileId: Option[String])
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import org.json4s._
|
|||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import model.Account
|
import model.Account
|
||||||
import scala.Some
|
import service.{SystemSettingsService, AccountService}
|
||||||
import service.AccountService
|
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpSession, HttpServletRequest}
|
import javax.servlet.http.{HttpServletResponse, HttpSession, HttpServletRequest}
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||||
@@ -21,7 +20,8 @@ import org.scalatra.i18n._
|
|||||||
* Provides generic features for controller implementations.
|
* Provides generic features for controller implementations.
|
||||||
*/
|
*/
|
||||||
abstract class ControllerBase extends ScalatraFilter
|
abstract class ControllerBase extends ScalatraFilter
|
||||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with Validations {
|
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||||
|
with SystemSettingsService {
|
||||||
|
|
||||||
implicit val jsonFormats = DefaultFormats
|
implicit val jsonFormats = DefaultFormats
|
||||||
|
|
||||||
@@ -58,11 +58,7 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
/**
|
/**
|
||||||
* Returns the context object for the request.
|
* Returns the context object for the request.
|
||||||
*/
|
*/
|
||||||
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, currentURL, request)
|
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, request)
|
||||||
|
|
||||||
private def currentURL: String = defining(request.getQueryString){ queryString =>
|
|
||||||
request.getRequestURI + (if(queryString != null) "?" + queryString else "")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount)
|
private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount)
|
||||||
|
|
||||||
@@ -107,27 +103,27 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
if(request.getMethod.toUpperCase == "POST"){
|
if(request.getMethod.toUpperCase == "POST"){
|
||||||
org.scalatra.Unauthorized(redirect("/signin"))
|
org.scalatra.Unauthorized(redirect("/signin"))
|
||||||
} else {
|
} else {
|
||||||
org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode(currentURL)))
|
org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode(
|
||||||
|
defining(request.getQueryString){ queryString =>
|
||||||
|
request.getRequestURI.substring(request.getContextPath.length) + (if(queryString != null) "?" + queryString else "")
|
||||||
|
}
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected def baseUrl = defining(request.getRequestURL.toString){ url =>
|
override def fullUrl(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||||
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
|
includeContextPath: Boolean = true, includeServletPath: Boolean = true)
|
||||||
}
|
(implicit request: HttpServletRequest, response: HttpServletResponse) =
|
||||||
|
if (path.startsWith("http")) path
|
||||||
|
else baseUrl + url(path, params, false, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context object for the current request.
|
* Context object for the current request.
|
||||||
*/
|
*/
|
||||||
case class Context(path: String, loginAccount: Option[Account], currentUrl: String, request: HttpServletRequest){
|
case class Context(path: String, loginAccount: Option[Account], request: HttpServletRequest){
|
||||||
|
|
||||||
def redirectUrl = if(request.getParameter("redirect") != null){
|
|
||||||
request.getParameter("redirect")
|
|
||||||
} else {
|
|
||||||
currentUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get object from cache.
|
* Get object from cache.
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import org.apache.commons.io.FileUtils
|
|||||||
* This servlet saves uploaded file as temporary file and returns the unique id.
|
* This servlet saves uploaded file as temporary file and returns the unique id.
|
||||||
* You can get uploaded file using [[app.FileUploadControllerBase#getTemporaryFile()]] with this id.
|
* You can get uploaded file using [[app.FileUploadControllerBase#getTemporaryFile()]] with this id.
|
||||||
*/
|
*/
|
||||||
class FileUploadController extends ScalatraServlet
|
class FileUploadController extends ScalatraServlet with FileUploadSupport with FileUploadControllerBase {
|
||||||
with FileUploadSupport with FlashMapSupport with FileUploadControllerBase {
|
|
||||||
|
|
||||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +1,85 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util._
|
import util._
|
||||||
import util.Implicits._
|
import service._
|
||||||
import service._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
|
class IndexController extends IndexControllerBase
|
||||||
class IndexController extends IndexControllerBase
|
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
||||||
with RepositoryService with SystemSettingsService with ActivityService with AccountService
|
|
||||||
with UsersAuthenticator
|
trait IndexControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
||||||
trait IndexControllerBase extends ControllerBase {
|
|
||||||
self: RepositoryService with SystemSettingsService with ActivityService with AccountService with UsersAuthenticator =>
|
case class SignInForm(userName: String, password: String)
|
||||||
|
|
||||||
case class SignInForm(userName: String, password: String)
|
val form = mapping(
|
||||||
|
"userName" -> trim(label("Username", text(required))),
|
||||||
val form = mapping(
|
"password" -> trim(label("Password", text(required)))
|
||||||
"userName" -> trim(label("Username", text(required))),
|
)(SignInForm.apply)
|
||||||
"password" -> trim(label("Password", text(required)))
|
|
||||||
)(SignInForm.apply)
|
get("/"){
|
||||||
|
val loginAccount = context.loginAccount
|
||||||
get("/"){
|
|
||||||
val loginAccount = context.loginAccount
|
html.index(getRecentActivities(),
|
||||||
|
getVisibleRepositories(loginAccount, baseUrl),
|
||||||
html.index(getRecentActivities(),
|
loadSystemSettings(),
|
||||||
getVisibleRepositories(loginAccount, baseUrl),
|
loginAccount.map{ account => getUserRepositories(account.userName, baseUrl) }.getOrElse(Nil)
|
||||||
loadSystemSettings(),
|
)
|
||||||
loginAccount.map{ account => getUserRepositories(account.userName, baseUrl) }.getOrElse(Nil)
|
}
|
||||||
)
|
|
||||||
}
|
get("/signin"){
|
||||||
|
val redirect = params.get("redirect")
|
||||||
get("/signin"){
|
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||||
val redirect = params.get("redirect")
|
flash += Keys.Flash.Redirect -> redirect.get
|
||||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
}
|
||||||
session.setAttribute(Keys.Session.Redirect, redirect.get)
|
html.signin(loadSystemSettings())
|
||||||
}
|
}
|
||||||
html.signin(loadSystemSettings())
|
|
||||||
}
|
post("/signin", form){ form =>
|
||||||
|
authenticate(loadSystemSettings(), form.userName, form.password) match {
|
||||||
post("/signin", form){ form =>
|
case Some(account) => signin(account)
|
||||||
authenticate(loadSystemSettings(), form.userName, form.password) match {
|
case None => redirect("/signin")
|
||||||
case Some(account) => signin(account)
|
}
|
||||||
case None => redirect("/signin")
|
}
|
||||||
}
|
|
||||||
}
|
get("/signout"){
|
||||||
|
session.invalidate
|
||||||
get("/signout"){
|
redirect("/")
|
||||||
session.invalidate
|
}
|
||||||
redirect("/")
|
|
||||||
}
|
/**
|
||||||
|
* Set account information into HttpSession and redirect.
|
||||||
/**
|
*/
|
||||||
* Set account information into HttpSession and redirect.
|
private def signin(account: model.Account) = {
|
||||||
*/
|
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||||
private def signin(account: model.Account) = {
|
updateLastLoginDate(account.userName)
|
||||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
|
||||||
updateLastLoginDate(account.userName)
|
if(AccountUtil.hasLdapDummyMailAddress(account)) {
|
||||||
|
redirect("/" + account.userName + "/_edit")
|
||||||
if(AccountUtil.hasLdapDummyMailAddress(account)) {
|
}
|
||||||
session.remove(Keys.Session.Redirect)
|
|
||||||
redirect("/" + account.userName + "/_edit")
|
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
||||||
}
|
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
|
||||||
|
redirect("/")
|
||||||
session.getAndRemove[String](Keys.Session.Redirect).map { redirectUrl =>
|
} else {
|
||||||
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
|
redirect(redirectUrl)
|
||||||
redirect("/")
|
}
|
||||||
} else {
|
}.getOrElse {
|
||||||
redirect(redirectUrl)
|
redirect("/")
|
||||||
}
|
}
|
||||||
}.getOrElse {
|
}
|
||||||
redirect("/")
|
|
||||||
}
|
/**
|
||||||
}
|
* JSON API for collaborator completion.
|
||||||
|
*
|
||||||
/**
|
* TODO Move to other controller?
|
||||||
* JSON API for collaborator completion.
|
*/
|
||||||
*
|
get("/_user/proposals")(usersOnly {
|
||||||
* TODO Move to other controller?
|
contentType = formats("json")
|
||||||
*/
|
org.json4s.jackson.Serialization.write(
|
||||||
get("/_user/proposals")(usersOnly {
|
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
||||||
contentType = formats("json")
|
)
|
||||||
org.json4s.jackson.Serialization.write(
|
})
|
||||||
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
|
||||||
)
|
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import jp.sf.amateras.scalatra.forms._
|
|||||||
|
|
||||||
import service._
|
import service._
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator, Notifier, Keys}
|
import util._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
|
import model.Issue
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
class IssuesController extends IssuesControllerBase
|
||||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
||||||
@@ -110,6 +111,11 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
// record activity
|
// record activity
|
||||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
||||||
|
|
||||||
|
// extract references and create refer comment
|
||||||
|
getIssue(owner, name, issueId.toString).foreach { issue =>
|
||||||
|
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
||||||
|
}
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
||||||
Notifier.msgIssue(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
Notifier.msgIssue(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||||
@@ -123,7 +129,11 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
getIssue(owner, name, params("id")).map { issue =>
|
||||||
if(isEditable(owner, name, issue.openedUserName)){
|
if(isEditable(owner, name, issue.openedUserName)){
|
||||||
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
||||||
|
// extract references and create refer comment
|
||||||
|
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
@@ -274,6 +284,15 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
redirect(s"/${repository.owner}/${repository.name}/issues")
|
redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
||||||
|
StringUtil.extractIssueId(message).foreach { issueId =>
|
||||||
|
if(getIssue(owner, repository, issueId).isDefined){
|
||||||
|
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
|
||||||
|
fromIssue.issueId + ":" + fromIssue.title, "refer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
||||||
*/
|
*/
|
||||||
@@ -313,6 +332,11 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
||||||
|
|
||||||
|
// extract references and create refer comment
|
||||||
|
content.map { content =>
|
||||||
|
createReferComment(owner, name, issue, content)
|
||||||
|
}
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier() match {
|
Notifier() match {
|
||||||
case f =>
|
case f =>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
pulls.html.pullreq(
|
pulls.html.pullreq(
|
||||||
issue, pullreq,
|
issue, pullreq,
|
||||||
getComments(owner, name, issueId),
|
getComments(owner, name, issueId),
|
||||||
getIssueLabels(owner, name, issueId.toInt),
|
getIssueLabels(owner, name, issueId),
|
||||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||||
getMilestonesWithIssueCount(owner, name),
|
getMilestonesWithIssueCount(owner, name),
|
||||||
getLabels(owner, name),
|
getLabels(owner, name),
|
||||||
@@ -183,6 +183,18 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close issue by content of pull request
|
||||||
|
val defaultBranch = getRepository(owner, name, baseUrl).get.repository.defaultBranch
|
||||||
|
if(pullreq.branch == defaultBranch){
|
||||||
|
commits.flatten.foreach { commit =>
|
||||||
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
|
}
|
||||||
|
issue.content match {
|
||||||
|
case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
||||||
|
}
|
||||||
// call web hook
|
// call web hook
|
||||||
getWebHookURLs(owner, name) match {
|
getWebHookURLs(owner, name) match {
|
||||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import util.Directory._
|
|||||||
import util.{UsersAuthenticator, OwnerAuthenticator}
|
import util.{UsersAuthenticator, OwnerAuthenticator}
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.scalatra.FlashMapSupport
|
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import service.WebHookService.WebHookPayload
|
import service.WebHookService.WebHookPayload
|
||||||
import util.JGitUtil.CommitInfo
|
import util.JGitUtil.CommitInfo
|
||||||
@@ -16,7 +15,7 @@ class RepositorySettingsController extends RepositorySettingsControllerBase
|
|||||||
with RepositoryService with AccountService with WebHookService
|
with RepositoryService with AccountService with WebHookService
|
||||||
with OwnerAuthenticator with UsersAuthenticator
|
with OwnerAuthenticator with UsersAuthenticator
|
||||||
|
|
||||||
trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSupport {
|
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with WebHookService
|
self: RepositoryService with AccountService with WebHookService
|
||||||
with OwnerAuthenticator with UsersAuthenticator =>
|
with OwnerAuthenticator with UsersAuthenticator =>
|
||||||
|
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val readme = files.find { file =>
|
val readme = files.find { file =>
|
||||||
readmeFiles.contains(file.name.toLowerCase)
|
readmeFiles.contains(file.name.toLowerCase)
|
||||||
}.map { file =>
|
}.map { file =>
|
||||||
StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
file -> StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.html.files(revision, repository,
|
repo.html.files(revision, repository,
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ import service._
|
|||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
class SearchController extends SearchControllerBase
|
class SearchController extends SearchControllerBase
|
||||||
with RepositoryService with AccountService with SystemSettingsService with ActivityService
|
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator
|
||||||
with RepositorySearchService with IssuesService
|
|
||||||
with ReferrerAuthenticator
|
|
||||||
|
|
||||||
trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
||||||
with SystemSettingsService with ActivityService with RepositorySearchService
|
with ActivityService with RepositorySearchService with ReferrerAuthenticator =>
|
||||||
with ReferrerAuthenticator =>
|
|
||||||
|
|
||||||
val searchForm = mapping(
|
val searchForm = mapping(
|
||||||
"query" -> trim(text(required)),
|
"query" -> trim(text(required)),
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import service.{AccountService, SystemSettingsService}
|
|||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
import util.AdminAuthenticator
|
import util.AdminAuthenticator
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.scalatra.FlashMapSupport
|
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with SystemSettingsService with AccountService with AdminAuthenticator
|
with SystemSettingsService with AccountService with AdminAuthenticator
|
||||||
|
|
||||||
trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport {
|
trait SystemSettingsControllerBase extends ControllerBase {
|
||||||
self: SystemSettingsService with AccountService with AdminAuthenticator =>
|
self: SystemSettingsService with AccountService with AdminAuthenticator =>
|
||||||
|
|
||||||
private val form = mapping(
|
private val form = mapping(
|
||||||
|
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||||
"notification" -> trim(label("Notification", boolean())),
|
"notification" -> trim(label("Notification", boolean())),
|
||||||
|
|||||||
@@ -6,18 +6,15 @@ import util.Directory._
|
|||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.scalatra.FlashMapSupport
|
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import scala.Some
|
import scala.Some
|
||||||
import java.util.ResourceBundle
|
import java.util.ResourceBundle
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
class WikiController extends WikiControllerBase
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService
|
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
||||||
with CollaboratorsAuthenticator with ReferrerAuthenticator
|
|
||||||
|
|
||||||
trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
trait WikiControllerBase extends ControllerBase {
|
||||||
self: WikiService with RepositoryService with ActivityService
|
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
||||||
with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
|
||||||
|
|
||||||
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
|
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ trait AccountService {
|
|||||||
Query(Accounts) filter(t => (t.userName is userName.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
|
Query(Accounts) filter(t => (t.userName is userName.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
|
||||||
|
|
||||||
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false): Option[Account] =
|
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false): Option[Account] =
|
||||||
Query(Accounts) filter(t => (t.mailAddress is mailAddress.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
|
Query(Accounts) filter(t => (t.mailAddress.toLowerCase is mailAddress.toLowerCase.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
|
||||||
|
|
||||||
def getAllUsers(includeRemoved: Boolean = true): List[Account] =
|
def getAllUsers(includeRemoved: Boolean = true): List[Account] =
|
||||||
if(includeRemoved){
|
if(includeRemoved){
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Q.interpolation
|
|||||||
import model._
|
import model._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
import util.StringUtil._
|
import util.StringUtil._
|
||||||
|
import util.StringUtil
|
||||||
|
|
||||||
trait IssuesService {
|
trait IssuesService {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
@@ -314,6 +315,14 @@ trait IssuesService {
|
|||||||
}.toList
|
}.toList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = {
|
||||||
|
StringUtil.extractCloseId(message).foreach { issueId =>
|
||||||
|
for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
|
||||||
|
createComment(owner, repository, userName, issue.issueId, "Close", "close")
|
||||||
|
updateClosed(owner, repository, issue.issueId, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object IssuesService {
|
object IssuesService {
|
||||||
|
|||||||
@@ -1,172 +1,183 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
trait SystemSettingsService {
|
|
||||||
|
trait SystemSettingsService {
|
||||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
|
||||||
defining(new java.util.Properties()){ props =>
|
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl.getOrElse {
|
||||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
defining(request.getRequestURL.toString){ url =>
|
||||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
|
||||||
props.setProperty(Notification, settings.notification.toString)
|
}
|
||||||
if(settings.notification) {
|
}.replaceFirst("/$", "")
|
||||||
settings.smtp.foreach { smtp =>
|
|
||||||
props.setProperty(SmtpHost, smtp.host)
|
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
defining(new java.util.Properties()){ props =>
|
||||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
settings.baseUrl.foreach(props.setProperty(BaseURL, _))
|
||||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||||
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
|
props.setProperty(Notification, settings.notification.toString)
|
||||||
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
|
if(settings.notification) {
|
||||||
}
|
settings.smtp.foreach { smtp =>
|
||||||
}
|
props.setProperty(SmtpHost, smtp.host)
|
||||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||||
if(settings.ldapAuthentication){
|
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||||
settings.ldap.map { ldap =>
|
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||||
props.setProperty(LdapHost, ldap.host)
|
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
|
||||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
|
||||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
}
|
||||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
}
|
||||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||||
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
if(settings.ldapAuthentication){
|
||||||
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
settings.ldap.map { ldap =>
|
||||||
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
props.setProperty(LdapHost, ldap.host)
|
||||||
ldap.disableMailResolve.foreach(x => props.setProperty(LdapDisableMailResolve, x.toString))
|
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||||
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||||
}
|
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||||
}
|
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||||
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
||||||
}
|
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
||||||
}
|
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
||||||
|
ldap.disableMailResolve.foreach(x => props.setProperty(LdapDisableMailResolve, x.toString))
|
||||||
|
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
||||||
def loadSystemSettings(): SystemSettings = {
|
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||||
defining(new java.util.Properties()){ props =>
|
}
|
||||||
if(GitBucketConf.exists){
|
}
|
||||||
props.load(new java.io.FileInputStream(GitBucketConf))
|
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
||||||
}
|
}
|
||||||
SystemSettings(
|
}
|
||||||
getValue(props, AllowAccountRegistration, false),
|
|
||||||
getValue(props, Gravatar, true),
|
|
||||||
getValue(props, Notification, false),
|
def loadSystemSettings(): SystemSettings = {
|
||||||
if(getValue(props, Notification, false)){
|
defining(new java.util.Properties()){ props =>
|
||||||
Some(Smtp(
|
if(GitBucketConf.exists){
|
||||||
getValue(props, SmtpHost, ""),
|
props.load(new java.io.FileInputStream(GitBucketConf))
|
||||||
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
}
|
||||||
getOptionValue(props, SmtpUser, None),
|
SystemSettings(
|
||||||
getOptionValue(props, SmtpPassword, None),
|
getOptionValue(props, BaseURL, None),
|
||||||
getOptionValue[Boolean](props, SmtpSsl, None),
|
getValue(props, AllowAccountRegistration, false),
|
||||||
getOptionValue(props, SmtpFromAddress, None),
|
getValue(props, Gravatar, true),
|
||||||
getOptionValue(props, SmtpFromName, None)))
|
getValue(props, Notification, false),
|
||||||
} else {
|
if(getValue(props, Notification, false)){
|
||||||
None
|
Some(Smtp(
|
||||||
},
|
getValue(props, SmtpHost, ""),
|
||||||
getValue(props, LdapAuthentication, false),
|
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
||||||
if(getValue(props, LdapAuthentication, false)){
|
getOptionValue(props, SmtpUser, None),
|
||||||
Some(Ldap(
|
getOptionValue(props, SmtpPassword, None),
|
||||||
getValue(props, LdapHost, ""),
|
getOptionValue[Boolean](props, SmtpSsl, None),
|
||||||
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
getOptionValue(props, SmtpFromAddress, None),
|
||||||
getOptionValue(props, LdapBindDN, None),
|
getOptionValue(props, SmtpFromName, None)))
|
||||||
getOptionValue(props, LdapBindPassword, None),
|
} else {
|
||||||
getValue(props, LdapBaseDN, ""),
|
None
|
||||||
getValue(props, LdapUserNameAttribute, ""),
|
},
|
||||||
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
getValue(props, LdapAuthentication, false),
|
||||||
getOptionValue(props, LdapFullNameAttribute, None),
|
if(getValue(props, LdapAuthentication, false)){
|
||||||
getValue(props, LdapMailAddressAttribute, ""),
|
Some(Ldap(
|
||||||
getOptionValue[Boolean](props, LdapDisableMailResolve, None),
|
getValue(props, LdapHost, ""),
|
||||||
getOptionValue[Boolean](props, LdapTls, None),
|
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
||||||
getOptionValue(props, LdapKeystore, None)))
|
getOptionValue(props, LdapBindDN, None),
|
||||||
} else {
|
getOptionValue(props, LdapBindPassword, None),
|
||||||
None
|
getValue(props, LdapBaseDN, ""),
|
||||||
}
|
getValue(props, LdapUserNameAttribute, ""),
|
||||||
)
|
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
||||||
}
|
getOptionValue(props, LdapFullNameAttribute, None),
|
||||||
}
|
getValue(props, LdapMailAddressAttribute, ""),
|
||||||
|
getOptionValue[Boolean](props, LdapDisableMailResolve, None),
|
||||||
}
|
getOptionValue[Boolean](props, LdapTls, None),
|
||||||
|
getOptionValue(props, LdapKeystore, None)))
|
||||||
object SystemSettingsService {
|
} else {
|
||||||
import scala.reflect.ClassTag
|
None
|
||||||
|
}
|
||||||
case class SystemSettings(
|
)
|
||||||
allowAccountRegistration: Boolean,
|
}
|
||||||
gravatar: Boolean,
|
}
|
||||||
notification: Boolean,
|
|
||||||
smtp: Option[Smtp],
|
}
|
||||||
ldapAuthentication: Boolean,
|
|
||||||
ldap: Option[Ldap])
|
object SystemSettingsService {
|
||||||
|
import scala.reflect.ClassTag
|
||||||
case class Ldap(
|
|
||||||
host: String,
|
case class SystemSettings(
|
||||||
port: Option[Int],
|
baseUrl: Option[String],
|
||||||
bindDN: Option[String],
|
allowAccountRegistration: Boolean,
|
||||||
bindPassword: Option[String],
|
gravatar: Boolean,
|
||||||
baseDN: String,
|
notification: Boolean,
|
||||||
userNameAttribute: String,
|
smtp: Option[Smtp],
|
||||||
additionalFilterCondition: Option[String],
|
ldapAuthentication: Boolean,
|
||||||
fullNameAttribute: Option[String],
|
ldap: Option[Ldap])
|
||||||
mailAttribute: String,
|
|
||||||
disableMailResolve: Option[Boolean],
|
case class Ldap(
|
||||||
tls: Option[Boolean],
|
host: String,
|
||||||
keystore: Option[String])
|
port: Option[Int],
|
||||||
|
bindDN: Option[String],
|
||||||
case class Smtp(
|
bindPassword: Option[String],
|
||||||
host: String,
|
baseDN: String,
|
||||||
port: Option[Int],
|
userNameAttribute: String,
|
||||||
user: Option[String],
|
additionalFilterCondition: Option[String],
|
||||||
password: Option[String],
|
fullNameAttribute: Option[String],
|
||||||
ssl: Option[Boolean],
|
mailAttribute: String,
|
||||||
fromAddress: Option[String],
|
disableMailResolve: Option[Boolean],
|
||||||
fromName: Option[String])
|
tls: Option[Boolean],
|
||||||
|
keystore: Option[String])
|
||||||
val DefaultSmtpPort = 25
|
|
||||||
val DefaultLdapPort = 389
|
case class Smtp(
|
||||||
|
host: String,
|
||||||
private val AllowAccountRegistration = "allow_account_registration"
|
port: Option[Int],
|
||||||
private val Gravatar = "gravatar"
|
user: Option[String],
|
||||||
private val Notification = "notification"
|
password: Option[String],
|
||||||
private val SmtpHost = "smtp.host"
|
ssl: Option[Boolean],
|
||||||
private val SmtpPort = "smtp.port"
|
fromAddress: Option[String],
|
||||||
private val SmtpUser = "smtp.user"
|
fromName: Option[String])
|
||||||
private val SmtpPassword = "smtp.password"
|
|
||||||
private val SmtpSsl = "smtp.ssl"
|
val DefaultSmtpPort = 25
|
||||||
private val SmtpFromAddress = "smtp.from_address"
|
val DefaultLdapPort = 389
|
||||||
private val SmtpFromName = "smtp.from_name"
|
|
||||||
private val LdapAuthentication = "ldap_authentication"
|
private val BaseURL = "base_url"
|
||||||
private val LdapHost = "ldap.host"
|
private val AllowAccountRegistration = "allow_account_registration"
|
||||||
private val LdapPort = "ldap.port"
|
private val Gravatar = "gravatar"
|
||||||
private val LdapBindDN = "ldap.bindDN"
|
private val Notification = "notification"
|
||||||
private val LdapBindPassword = "ldap.bind_password"
|
private val SmtpHost = "smtp.host"
|
||||||
private val LdapBaseDN = "ldap.baseDN"
|
private val SmtpPort = "smtp.port"
|
||||||
private val LdapUserNameAttribute = "ldap.username_attribute"
|
private val SmtpUser = "smtp.user"
|
||||||
private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
|
private val SmtpPassword = "smtp.password"
|
||||||
private val LdapFullNameAttribute = "ldap.fullname_attribute"
|
private val SmtpSsl = "smtp.ssl"
|
||||||
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
private val SmtpFromAddress = "smtp.from_address"
|
||||||
private val LdapDisableMailResolve = "ldap.disable_mail_resolve"
|
private val SmtpFromName = "smtp.from_name"
|
||||||
private val LdapTls = "ldap.tls"
|
private val LdapAuthentication = "ldap_authentication"
|
||||||
private val LdapKeystore = "ldap.keystore"
|
private val LdapHost = "ldap.host"
|
||||||
|
private val LdapPort = "ldap.port"
|
||||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
|
private val LdapBindDN = "ldap.bindDN"
|
||||||
defining(props.getProperty(key)){ value =>
|
private val LdapBindPassword = "ldap.bind_password"
|
||||||
if(value == null || value.isEmpty) default
|
private val LdapBaseDN = "ldap.baseDN"
|
||||||
else convertType(value).asInstanceOf[A]
|
private val LdapUserNameAttribute = "ldap.username_attribute"
|
||||||
}
|
private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
|
||||||
|
private val LdapFullNameAttribute = "ldap.fullname_attribute"
|
||||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
|
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
||||||
defining(props.getProperty(key)){ value =>
|
private val LdapDisableMailResolve = "ldap.disable_mail_resolve"
|
||||||
if(value == null || value.isEmpty) default
|
private val LdapTls = "ldap.tls"
|
||||||
else Some(convertType(value)).asInstanceOf[Option[A]]
|
private val LdapKeystore = "ldap.keystore"
|
||||||
}
|
|
||||||
|
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
|
||||||
private def convertType[A: ClassTag](value: String) =
|
defining(props.getProperty(key)){ value =>
|
||||||
defining(implicitly[ClassTag[A]].runtimeClass){ c =>
|
if(value == null || value.isEmpty) default
|
||||||
if(c == classOf[Boolean]) value.toBoolean
|
else convertType(value).asInstanceOf[A]
|
||||||
else if(c == classOf[Int]) value.toInt
|
}
|
||||||
else value
|
|
||||||
}
|
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
|
||||||
|
defining(props.getProperty(key)){ value =>
|
||||||
}
|
if(value == null || value.isEmpty) default
|
||||||
|
else Some(convertType(value)).asInstanceOf[Option[A]]
|
||||||
|
}
|
||||||
|
|
||||||
|
private def convertType[A: ClassTag](value: String) =
|
||||||
|
defining(implicitly[ClassTag[A]].runtimeClass){ c =>
|
||||||
|
if(c == classOf[Boolean]) value.toBoolean
|
||||||
|
else if(c == classOf[Int]) value.toInt
|
||||||
|
else value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ object AutoUpdate {
|
|||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
*/
|
*/
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
|
Version(1, 11),
|
||||||
Version(1, 10),
|
Version(1, 10),
|
||||||
Version(1, 9),
|
Version(1, 9),
|
||||||
Version(1, 8),
|
Version(1, 8),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory
|
|||||||
import javax.servlet.ServletConfig
|
import javax.servlet.ServletConfig
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import util.{Keys, JGitUtil, Directory}
|
import util.{StringUtil, Keys, JGitUtil, Directory}
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
import service._
|
import service._
|
||||||
@@ -50,10 +50,10 @@ class GitRepositoryServlet extends GitServlet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] {
|
class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] with SystemSettingsService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitBucketReceivePackFactory])
|
private val logger = LoggerFactory.getLogger(classOf[GitBucketReceivePackFactory])
|
||||||
|
|
||||||
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
|
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
|
||||||
val receivePack = new ReceivePack(db)
|
val receivePack = new ReceivePack(db)
|
||||||
val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String]
|
val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String]
|
||||||
@@ -64,13 +64,11 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
defining(request.paths){ paths =>
|
defining(request.paths){ paths =>
|
||||||
val owner = paths(1)
|
val owner = paths(1)
|
||||||
val repository = paths(2).replaceFirst("\\.git$", "")
|
val repository = paths(2).replaceFirst("\\.git$", "")
|
||||||
val baseURL = request.getRequestURL.toString.replaceFirst("/git/.*", "")
|
|
||||||
|
|
||||||
logger.debug("repository:" + owner + "/" + repository)
|
logger.debug("repository:" + owner + "/" + repository)
|
||||||
logger.debug("baseURL:" + baseURL)
|
|
||||||
|
|
||||||
if(!repository.endsWith(".wiki")){
|
if(!repository.endsWith(".wiki")){
|
||||||
receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, pusher, baseURL))
|
receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, pusher, baseUrl(request)))
|
||||||
}
|
}
|
||||||
receivePack
|
receivePack
|
||||||
}
|
}
|
||||||
@@ -79,7 +77,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseURL: String) extends PostReceiveHook
|
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String) extends PostReceiveHook
|
||||||
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService {
|
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||||
@@ -143,12 +141,20 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseURL:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close issues
|
||||||
|
val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch
|
||||||
|
if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){
|
||||||
|
git.log.addRange(command.getOldId, command.getNewId).call.asScala.foreach { commit =>
|
||||||
|
closeIssuesFromMessage(commit.getFullMessage, pusher, owner, repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// call web hook
|
// call web hook
|
||||||
getWebHookURLs(owner, repository) match {
|
getWebHookURLs(owner, repository) match {
|
||||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||||
for(pusherAccount <- getAccountByUserName(pusher);
|
for(pusherAccount <- getAccountByUserName(pusher);
|
||||||
ownerAccount <- getAccountByUserName(owner);
|
ownerAccount <- getAccountByUserName(owner);
|
||||||
repositoryInfo <- getRepository(owner, repository, baseURL)){
|
repositoryInfo <- getRepository(owner, repository, baseUrl)){
|
||||||
callWebHook(owner, repository, webHookURLs,
|
callWebHook(owner, repository, webHookURLs,
|
||||||
WebHookPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount))
|
WebHookPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount))
|
||||||
}
|
}
|
||||||
@@ -167,8 +173,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseURL:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def createIssueComment(commit: CommitInfo) = {
|
private def createIssueComment(commit: CommitInfo) = {
|
||||||
"(^|\\W)#(\\d+)(\\W|$)".r.findAllIn(commit.fullMessage).matchData.foreach { matchData =>
|
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
|
||||||
val issueId = matchData.group(2)
|
|
||||||
if(getIssue(owner, repository, issueId).isDefined){
|
if(getIssue(owner, repository, issueId).isDefined){
|
||||||
getAccountByMailAddress(commit.mailAddress).foreach { account =>
|
getAccountByMailAddress(commit.mailAddress).foreach { account =>
|
||||||
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
||||||
@@ -182,7 +187,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseURL:
|
|||||||
*/
|
*/
|
||||||
private def updatePullRequests(branch: String) =
|
private def updatePullRequests(branch: String) =
|
||||||
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
|
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
|
||||||
if(getRepository(pullreq.userName, pullreq.repositoryName, baseURL).isDefined){
|
if(getRepository(pullreq.userName, pullreq.repositoryName, baseUrl).isDefined){
|
||||||
using(Git.open(Directory.getRepositoryDir(pullreq.userName, pullreq.repositoryName))){ git =>
|
using(Git.open(Directory.getRepositoryDir(pullreq.userName, pullreq.repositoryName))){ git =>
|
||||||
git.fetch
|
git.fetch
|
||||||
.setRemote(Directory.getRepositoryDir(owner, repository).toURI.toString)
|
.setRemote(Directory.getRepositoryDir(owner, repository).toURI.toString)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package util
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk
|
import org.eclipse.jgit.treewalk.TreeWalk
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import scala.util.control.Exception._
|
||||||
import scala.language.reflectiveCalls
|
import scala.language.reflectiveCalls
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,10 +16,8 @@ object ControlUtil {
|
|||||||
def using[A <% { def close(): Unit }, B](resource: A)(f: A => B): B =
|
def using[A <% { def close(): Unit }, B](resource: A)(f: A => B): B =
|
||||||
try f(resource) finally {
|
try f(resource) finally {
|
||||||
if(resource != null){
|
if(resource != null){
|
||||||
try {
|
ignoring(classOf[Throwable]) {
|
||||||
resource.close()
|
resource.close()
|
||||||
} catch {
|
|
||||||
case e: Throwable => // ignore
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import scala.util.matching.Regex
|
import scala.util.matching.Regex
|
||||||
|
import scala.util.control.Exception._
|
||||||
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,10 +43,8 @@ object Implicits {
|
|||||||
sb.toString
|
sb.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
def toIntOpt: Option[Int] = try {
|
def toIntOpt: Option[Int] = catching(classOf[NumberFormatException]) opt {
|
||||||
Option(Integer.parseInt(value))
|
Integer.parseInt(value)
|
||||||
} catch {
|
|
||||||
case e: NumberFormatException => None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ object JGitUtil {
|
|||||||
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||||
try {
|
try {
|
||||||
// get commit count
|
// get commit count
|
||||||
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(1000).sum
|
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10000).sum
|
||||||
|
|
||||||
RepositoryInfo(
|
RepositoryInfo(
|
||||||
owner, repository, s"${baseUrl}/git/${owner}/${repository}.git",
|
owner, repository, s"${baseUrl}/git/${owner}/${repository}.git",
|
||||||
|
|||||||
@@ -13,12 +13,7 @@ object Keys {
|
|||||||
/**
|
/**
|
||||||
* Session key for the logged in account information.
|
* Session key for the logged in account information.
|
||||||
*/
|
*/
|
||||||
val LoginAccount = "LOGIN_ACCOUNT"
|
val LoginAccount = "loginAccount"
|
||||||
|
|
||||||
/**
|
|
||||||
* Session key for the redirect URL.
|
|
||||||
*/
|
|
||||||
val Redirect = "REDIRECT"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session key for the issue search condition in dashboard.
|
* Session key for the issue search condition in dashboard.
|
||||||
@@ -47,6 +42,20 @@ object Keys {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Flash {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flash key for the redirect URL.
|
||||||
|
*/
|
||||||
|
val Redirect = "redirect"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flash key for the information message.
|
||||||
|
*/
|
||||||
|
val Info = "info"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define request keys.
|
* Define request keys.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ object StringUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Make string from byte array. Character encoding is detected automatically by [[util.StringUtil.detectEncoding]].
|
* Make string from byte array. Character encoding is detected automatically by [[util.StringUtil.detectEncoding]].
|
||||||
* And if given bytes contains UTF-8 BOM, it's removed from returned string..
|
* And if given bytes contains UTF-8 BOM, it's removed from returned string.
|
||||||
*/
|
*/
|
||||||
def convertFromByteArray(content: Array[Byte]): String =
|
def convertFromByteArray(content: Array[Byte]): String =
|
||||||
IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content))
|
IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content))
|
||||||
@@ -45,4 +45,23 @@ object StringUtil {
|
|||||||
case e => e
|
case e => e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract issue id like ```#issueId``` from the given message.
|
||||||
|
*
|
||||||
|
*@param message the message which may contains issue id
|
||||||
|
* @return the iterator of issue id
|
||||||
|
*/
|
||||||
|
def extractIssueId(message: String): Iterator[String] =
|
||||||
|
"(^|\\W)#(\\d+)(\\W|$)".r.findAllIn(message).matchData.map(_.group(2))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract close issue id like ```close #issueId ``` from the given message.
|
||||||
|
*
|
||||||
|
* @param message the message which may contains close command
|
||||||
|
* @return the iterator of issue id
|
||||||
|
*/
|
||||||
|
def extractCloseId(message: String): Iterator[String] =
|
||||||
|
"(?i)(?<!\\w)(?:fix(?:e[sd])?|resolve[sd]?|close[sd]?)\\s+#(\\d+)(?!\\w)".r.findAllIn(message).matchData.map(_.group(1))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,8 +116,9 @@ class GitBucketHtmlSerializer(
|
|||||||
val tag = s"h${node.getLevel}"
|
val tag = s"h${node.getLevel}"
|
||||||
val headerTextString = printChildrenToString(node)
|
val headerTextString = printChildrenToString(node)
|
||||||
val anchorName = GitBucketHtmlSerializer.generateAnchorName(headerTextString)
|
val anchorName = GitBucketHtmlSerializer.generateAnchorName(headerTextString)
|
||||||
printer.print(s"<$tag>")
|
printer.print(s"""<$tag class="markdown-head">""")
|
||||||
printer.print(s"""<a class="anchor" name="$anchorName" href="#$anchorName"></a>""")
|
printer.print(s"""<a class="markdown-anchor-link" href="#$anchorName"></a>""")
|
||||||
|
printer.print(s"""<a class="markdown-anchor" name="$anchorName"></a>""")
|
||||||
visitChildren(node)
|
visitChildren(node)
|
||||||
printer.print(s"</$tag>")
|
printer.print(s"</$tag>")
|
||||||
}
|
}
|
||||||
@@ -142,12 +143,10 @@ object GitBucketHtmlSerializer {
|
|||||||
|
|
||||||
private val Whitespace = "[\\s]".r
|
private val Whitespace = "[\\s]".r
|
||||||
|
|
||||||
private val SpecialChars = "[^\\w-]".r
|
|
||||||
|
|
||||||
def generateAnchorName(text: String): String = {
|
def generateAnchorName(text: String): String = {
|
||||||
val noWhitespace = Whitespace.replaceAllIn(text, "-")
|
val noWhitespace = Whitespace.replaceAllIn(text, "-")
|
||||||
val normalized = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD)
|
val normalized = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD)
|
||||||
val noSpecialChars = SpecialChars.replaceAllIn(normalized, "")
|
val noSpecialChars = StringUtil.urlEncode(normalized)
|
||||||
noSpecialChars.toLowerCase(Locale.ENGLISH)
|
noSpecialChars.toLowerCase(Locale.ENGLISH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,205 +3,220 @@
|
|||||||
@import util.Directory._
|
@import util.Directory._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main("System Settings"){
|
@html.main("System Settings"){
|
||||||
@menu("system"){
|
@menu("system"){
|
||||||
@helper.html.information(info)
|
@helper.html.information(info)
|
||||||
<form action="@path/admin/system" method="POST" validate="true">
|
<form action="@path/admin/system" method="POST" validate="true">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-header">System Settings</div>
|
<div class="box-header">System Settings</div>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- GITBUCKET_HOME -->
|
<!-- GITBUCKET_HOME -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<label class="strong">GITBUCKET_HOME</label>
|
<label class="strong">GITBUCKET_HOME</label>
|
||||||
@GitBucketHome
|
@GitBucketHome
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Account registration -->
|
<!-- Base URL -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
<label class="strong">Account registration</label>
|
<label><span class="strong">Base URL</span> (e.g. <code>http://example.com/gitbucket</code>)</label>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="radio">
|
<div class="controls">
|
||||||
<input type="radio" name="allowAccountRegistration" value="true"@if(settings.allowAccountRegistration){ checked}>
|
<input type="text" name="baseUrl" id="baseUrl" style="width: 400px" value="@settings.baseUrl"/>
|
||||||
<span class="strong">Allow</span> - Users can create accounts by themselves.
|
</div>
|
||||||
</label>
|
</fieldset>
|
||||||
<label class="radio">
|
<p>
|
||||||
<input type="radio" name="allowAccountRegistration" value="false"@if(!settings.allowAccountRegistration){ checked}>
|
The base URL is used for redirect, notification email, git repository URL box and more.
|
||||||
<span class="strong">Deny</span> - Only administrators can create accounts.
|
If the base URL is empty, GitBucket generates URL from request information.
|
||||||
</label>
|
You can use this property to adjust URL difference between the reverse proxy and GitBucket.
|
||||||
</fieldset>
|
</p>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Services -->
|
<!-- Account registration -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
<label class="strong">Services</label>
|
<label class="strong">Account registration</label>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="checkbox">
|
<label class="radio">
|
||||||
<input type="checkbox" name="gravatar"@if(settings.gravatar){ checked}/>
|
<input type="radio" name="allowAccountRegistration" value="true"@if(settings.allowAccountRegistration){ checked}>
|
||||||
Use Gravatar for Profile-Images
|
<span class="strong">Allow</span> - Users can create accounts by themselves.
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
<label class="radio">
|
||||||
<!--====================================================================-->
|
<input type="radio" name="allowAccountRegistration" value="false"@if(!settings.allowAccountRegistration){ checked}>
|
||||||
<!-- Authentication -->
|
<span class="strong">Deny</span> - Only administrators can create accounts.
|
||||||
<!--====================================================================-->
|
</label>
|
||||||
<hr>
|
</fieldset>
|
||||||
<label class="strong">Authentication</label>
|
<!--====================================================================-->
|
||||||
<fieldset>
|
<!-- Services -->
|
||||||
<label class="checkbox">
|
<!--====================================================================-->
|
||||||
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked}/>
|
<hr>
|
||||||
LDAP
|
<label class="strong">Services</label>
|
||||||
</label>
|
<fieldset>
|
||||||
</fieldset>
|
<label class="checkbox">
|
||||||
<div class="form-horizontal ldap">
|
<input type="checkbox" name="gravatar"@if(settings.gravatar){ checked}/>
|
||||||
<div class="control-group">
|
Use Gravatar for Profile-Images
|
||||||
<label class="control-label" for="ldapHost">LDAP Host</label>
|
</label>
|
||||||
<div class="controls">
|
</fieldset>
|
||||||
<input type="text" id="ldapHost" name="ldap.host" value="@settings.ldap.map(_.host)"/>
|
<!--====================================================================-->
|
||||||
<span id="error-ldap_host" class="error"></span>
|
<!-- Authentication -->
|
||||||
</div>
|
<!--====================================================================-->
|
||||||
</div>
|
<hr>
|
||||||
<div class="control-group">
|
<label class="strong">Authentication</label>
|
||||||
<label class="control-label" for="ldapPort">LDAP Port</label>
|
<fieldset>
|
||||||
<div class="controls">
|
<label class="checkbox">
|
||||||
<input type="text" id="ldapPort" name="ldap.port" class="input-mini" value="@settings.ldap.map(_.port)"/>
|
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked}/>
|
||||||
<span id="error-ldap_port" class="error"></span>
|
LDAP
|
||||||
</div>
|
</label>
|
||||||
</div>
|
</fieldset>
|
||||||
<div class="control-group">
|
<div class="form-horizontal ldap">
|
||||||
<label class="control-label" for="ldapBindDN">Bind DN</label>
|
<div class="control-group">
|
||||||
<div class="controls">
|
<label class="control-label" for="ldapHost">LDAP Host</label>
|
||||||
<input type="text" id="ldapBindDN" name="ldap.bindDN" value="@settings.ldap.map(_.bindDN)"/>
|
<div class="controls">
|
||||||
<span id="error-ldap_bindDN" class="error"></span>
|
<input type="text" id="ldapHost" name="ldap.host" value="@settings.ldap.map(_.host)"/>
|
||||||
</div>
|
<span id="error-ldap_host" class="error"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapBindPassword">Bind Password</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="password" id="ldapBindPassword" name="ldap.bindPassword" value="@settings.ldap.map(_.bindPassword)"/>
|
|
||||||
<span id="error-ldap_bindPassword" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapBaseDN">Base DN</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="ldapBaseDN" name="ldap.baseDN" value="@settings.ldap.map(_.baseDN)"/>
|
|
||||||
<span id="error-ldap_baseDN" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapUserNameAttribute">User name attribute</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="ldapUserNameAttribute" name="ldap.userNameAttribute" value="@settings.ldap.map(_.userNameAttribute)"/>
|
|
||||||
<span id="error-ldap_userNameAttribute" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapAdditionalFilterCondition">Additional filter condition</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" value="@settings.ldap.map(_.additionalFilterCondition)"/>
|
|
||||||
<span id="error-ldap_additionalFilterCondition" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="ldapFullNameAttribute" name="ldap.fullNameAttribute" value="@settings.ldap.map(_.fullNameAttribute)"/>
|
|
||||||
<span id="error-ldap_fullNameAttribute" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapMailAttribute">Mail address attribute</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="ldapMailAttribute" name="ldap.mailAttribute" value="@settings.ldap.map(_.mailAttribute)"/>
|
|
||||||
<span id="error-ldap_mailAttribute" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="controls">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" name="ldap.disableMailResolve"@if(settings.ldap.flatMap(_.disableMailResolve).getOrElse(false)){ checked}/> Disable Mail Resolve
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="controls">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" name="ldap.tls"@if(settings.ldap.flatMap(_.tls).getOrElse(false)){ checked}/> Enable TLS
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="ldapBindDN">Keystore</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="ldapKeystore" name="ldap.keystore" value="@settings.ldap.map(_.keystore)"/>
|
|
||||||
<span id="error-ldap_keystore" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!--====================================================================-->
|
<div class="control-group">
|
||||||
<!-- Notification email -->
|
<label class="control-label" for="ldapPort">LDAP Port</label>
|
||||||
<!--====================================================================-->
|
<div class="controls">
|
||||||
<hr>
|
<input type="text" id="ldapPort" name="ldap.port" class="input-mini" value="@settings.ldap.map(_.port)"/>
|
||||||
<label class="strong">Notification email</label>
|
<span id="error-ldap_port" class="error"></span>
|
||||||
<fieldset>
|
</div>
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" id="notification" name="notification"@if(settings.notification){ checked}/>
|
|
||||||
Send notifications
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<div class="form-horizontal notification">
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="smtpHost">SMTP Host</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="smtpHost" name="smtp.host" value="@settings.smtp.map(_.host)"/>
|
|
||||||
<span id="error-smtp_host" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="smtpPort">SMTP Port</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="smtpPort" name="smtp.port" class="input-mini" value="@settings.smtp.map(_.port)"/>
|
|
||||||
<span id="error-smtp_port" class="error"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="smtpUser">SMTP User</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="smtpUser" name="smtp.user" value="@settings.smtp.map(_.user)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="smtpPassword">SMTP Password</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="password" id="smtpPassword" name="smtp.password" value="@settings.smtp.map(_.password)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="controls">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/> Enable SSL
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="fromAddress">FROM Address</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="fromAddress" name="smtp.fromAddress" value="@settings.smtp.map(_.fromAddress)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="fromName">FROM Name</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" id="fromName" name="smtp.fromName" value="@settings.smtp.map(_.fromName)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapBindDN">Bind DN</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapBindDN" name="ldap.bindDN" value="@settings.ldap.map(_.bindDN)"/>
|
||||||
|
<span id="error-ldap_bindDN" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapBindPassword">Bind Password</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="password" id="ldapBindPassword" name="ldap.bindPassword" value="@settings.ldap.map(_.bindPassword)"/>
|
||||||
|
<span id="error-ldap_bindPassword" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapBaseDN">Base DN</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapBaseDN" name="ldap.baseDN" value="@settings.ldap.map(_.baseDN)"/>
|
||||||
|
<span id="error-ldap_baseDN" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapUserNameAttribute">User name attribute</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapUserNameAttribute" name="ldap.userNameAttribute" value="@settings.ldap.map(_.userNameAttribute)"/>
|
||||||
|
<span id="error-ldap_userNameAttribute" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapAdditionalFilterCondition">Full name attribute</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" value="@settings.ldap.map(_.additionalFilterCondition)"/>
|
||||||
|
<span id="error-ldap_additionalFilterCondition" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapFullNameAttribute" name="ldap.fullNameAttribute" value="@settings.ldap.map(_.fullNameAttribute)"/>
|
||||||
|
<span id="error-ldap_fullNameAttribute" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapMailAttribute">Mail address attribute</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapMailAttribute" name="ldap.mailAttribute" value="@settings.ldap.map(_.mailAttribute)"/>
|
||||||
|
<span id="error-ldap_mailAttribute" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" name="ldap.disableMailResolve"@if(settings.ldap.flatMap(_.disableMailResolve).getOrElse(false)){ checked}/> Disable mail resolve
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" name="ldap.tls"@if(settings.ldap.flatMap(_.tls).getOrElse(false)){ checked}/> Enable TLS
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="ldapBindDN">Keystore</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="ldapKeystore" name="ldap.keystore" value="@settings.ldap.map(_.keystore)"/>
|
||||||
|
<span id="error-ldap_keystore" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--====================================================================-->
|
||||||
|
<!-- Notification email -->
|
||||||
|
<!--====================================================================-->
|
||||||
|
<hr>
|
||||||
|
<label class="strong">Notification email</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">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="smtpHost">SMTP Host</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="smtpHost" name="smtp.host" value="@settings.smtp.map(_.host)"/>
|
||||||
|
<span id="error-smtp_host" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="smtpPort">SMTP Port</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="smtpPort" name="smtp.port" class="input-mini" value="@settings.smtp.map(_.port)"/>
|
||||||
|
<span id="error-smtp_port" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="smtpUser">SMTP User</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="smtpUser" name="smtp.user" value="@settings.smtp.map(_.user)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="smtpPassword">SMTP Password</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="password" id="smtpPassword" name="smtp.password" value="@settings.smtp.map(_.password)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/> Enable SSL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="fromAddress">FROM Address</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="fromAddress" name="smtp.fromAddress" value="@settings.smtp.map(_.fromAddress)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="fromName">FROM Name</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="fromName" name="smtp.fromName" value="@settings.smtp.map(_.fromName)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<input type="submit" class="btn btn-success" value="Apply changes"/>
|
<input type="submit" class="btn btn-success" value="Apply changes"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
@@ -213,4 +228,4 @@ $(function(){
|
|||||||
$('.ldap input').prop('disabled', !$(this).prop('checked'));
|
$('.ldap input').prop('disabled', !$(this).prop('checked'));
|
||||||
}).change();
|
}).change();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,10 +12,13 @@
|
|||||||
}
|
}
|
||||||
<div class="head">
|
<div class="head">
|
||||||
@if(repository.repository.isPrivate){
|
@if(repository.repository.isPrivate){
|
||||||
<i class="icon-lock"></i>
|
<img src="@assets/common/images/repo_private_lg.png"/>
|
||||||
}
|
} else {
|
||||||
@if(!repository.repository.isPrivate){
|
@if(repository.repository.originUserName.isDefined){
|
||||||
<i class="icon-eye-open"></i>
|
<img src="@assets/common/images/repo_fork_lg.png"/>
|
||||||
|
} else {
|
||||||
|
<img src="@assets/common/images/repo_public_lg.png"/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
|
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
|
||||||
|
|
||||||
@@ -27,6 +30,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@repository.repository.description.map { description =>
|
||||||
|
<p>@description</p>
|
||||||
|
}
|
||||||
<table class="global-nav box-header">
|
<table class="global-nav box-header">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="box-header@if(active=="code"){ active}">
|
<th class="box-header@if(active=="code"){ active}">
|
||||||
|
|||||||
@@ -32,10 +32,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@if(repository.repository.isPrivate){
|
@if(repository.repository.isPrivate){
|
||||||
<i class="icon-lock"></i>
|
<img src="@assets/common/images/repo_private.png"/>
|
||||||
}
|
} else {
|
||||||
@if(!repository.repository.isPrivate){
|
@if(repository.repository.originUserName.isDefined){
|
||||||
<i class="icon-eye-open"></i>
|
<img src="@assets/common/images/repo_fork.png"/>
|
||||||
|
} else {
|
||||||
|
<img src="@assets/common/images/repo_public.png"/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@if(repository.owner == loginAccount.get.userName){
|
@if(repository.owner == loginAccount.get.userName){
|
||||||
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
|
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
|
||||||
@@ -64,10 +67,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@if(repository.repository.isPrivate){
|
@if(repository.repository.isPrivate){
|
||||||
<i class="icon-lock"></i>
|
<img src="@assets/common/images/repo_private.png"/>
|
||||||
}
|
} else {
|
||||||
@if(!repository.repository.isPrivate){
|
@if(repository.repository.originUserName.isDefined){
|
||||||
<i class="icon-eye-open"></i>
|
<img src="@assets/common/images/repo_fork.png"/>
|
||||||
|
} else {
|
||||||
|
<img src="@assets/common/images/repo_public.png"/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -11,10 +11,16 @@
|
|||||||
<div class="box issue-comment-box" id="comment-@comment.commentId">
|
<div class="box issue-comment-box" id="comment-@comment.commentId">
|
||||||
<div class="box-header-small">
|
<div class="box-header-small">
|
||||||
<i class="icon-comment"></i>
|
<i class="icon-comment"></i>
|
||||||
@user(comment.commentedUserName, styleClass="username strong") commented
|
@user(comment.commentedUserName, styleClass="username strong")
|
||||||
|
@if(comment.action == "comment"){
|
||||||
|
commented
|
||||||
|
} else {
|
||||||
|
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
|
||||||
|
}
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
@datetime(comment.registeredDate)
|
@datetime(comment.registeredDate)
|
||||||
@if(comment.action != "commit" && comment.action != "merge" && (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
@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"></i></a>
|
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>
|
||||||
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a>
|
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a>
|
||||||
}
|
}
|
||||||
@@ -27,7 +33,13 @@
|
|||||||
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true)
|
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@markdown(comment.content, repository, false, true)
|
@if(comment.action == "refer"){
|
||||||
|
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
|
||||||
|
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@markdown(comment.content, repository, false, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
<a href="@path/signout" class="menu-last" data-toggle="tooltip" data-placement="bottom" title="Sign out"><i class="icon-share-alt"></i></a>
|
<a href="@path/signout" class="menu-last" data-toggle="tooltip" data-placement="bottom" title="Sign out"><i class="icon-share-alt"></i></a>
|
||||||
} else {
|
} else {
|
||||||
<a href="@path/signin?redirect=@redirectUrl" class="btn btn-last">Sign in</a>
|
<a href="@path/signin" class="btn btn-last" id="signin">Sign in</a>
|
||||||
}
|
}
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
</div>
|
</div>
|
||||||
@@ -76,6 +76,7 @@
|
|||||||
$('#search').submit(function(){
|
$('#search').submit(function(){
|
||||||
return $.trim($(this).find('input[name=query]').val()) != '';
|
return $.trim($(this).find('input[name=query]').val()) != '';
|
||||||
});
|
});
|
||||||
|
$('#signin').attr('href', '@path/signin?redirect=' + encodeURIComponent(location.pathname + location.search + location.hash));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<fieldset class="margin">
|
<fieldset class="margin">
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" name="isPrivate" value="false" checked>
|
<input type="radio" name="isPrivate" value="false" checked>
|
||||||
<span class="strong"><i class="icon-eye-open"> </i> Public</span><br>
|
<span class="strong"><img src="@assets/common/images/repo_public.png"/> </i> Public</span><br>
|
||||||
<div>
|
<div>
|
||||||
<span>All users and guests can read this repository.</span>
|
<span>All users and guests can read this repository.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" name="isPrivate" value="true">
|
<input type="radio" name="isPrivate" value="true">
|
||||||
<span class="strong"><i class="icon-lock"> </i> Private</span><br>
|
<span class="strong"><img src="@assets/common/images/repo_private.png"/> </i> Private</span><br>
|
||||||
<div>
|
<div>
|
||||||
<span>Only collaborators can read this repository.</span>
|
<span>Only collaborators can read this repository.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
pathList: List[String],
|
pathList: List[String],
|
||||||
latestCommit: util.JGitUtil.CommitInfo,
|
latestCommit: util.JGitUtil.CommitInfo,
|
||||||
files: List[util.JGitUtil.FileInfo],
|
files: List[util.JGitUtil.FileInfo],
|
||||||
readme: Option[String])(implicit context: app.Context)
|
readme: Option[(util.JGitUtil.FileInfo, String)])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<div class="head">
|
<div class="head">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@defining(repository.commitCount){ commitCount =>
|
@defining(repository.commitCount){ commitCount =>
|
||||||
<a href="@url(repository)/commits/@encodeRefName(branch)">@if(commitCount > 1000){ @commitCount+ } else { @commitCount } @plural(commitCount, "commit")</a>
|
<a href="@url(repository)/commits/@encodeRefName(branch)">@if(commitCount > 10000){ @commitCount+ } else { @commitCount } @plural(commitCount, "commit")</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
||||||
@@ -77,9 +77,9 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@readme.map { content =>
|
@readme.map { case(file, content) =>
|
||||||
<div id="readme" class="box">
|
<div id="readme" class="box">
|
||||||
<div class="box-header">README.md</div>
|
<div class="box-header">@file.name</div>
|
||||||
<div class="box-content markdown-body">@markdown(content, repository, false, false)</div>
|
<div class="box-content markdown-body">@markdown(content, repository, false, false)</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -850,3 +850,21 @@ div.markdown-body table colgroup + tbody tr:first-child td:last-child {
|
|||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
-moz-border-radius-topright: 4px;
|
-moz-border-radius-topright: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-head {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.markdown-anchor-link {
|
||||||
|
position: absolute;
|
||||||
|
left: -20px;
|
||||||
|
width: 32px;
|
||||||
|
height: 16px;
|
||||||
|
background-image: url(../images/link.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 a.markdown-anchor-link, h2 a.markdown-anchor-link, h3 a.markdown-anchor-link {
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|||||||
BIN
src/main/webapp/assets/common/images/link.png
Normal file
|
After Width: | Height: | Size: 343 B |
BIN
src/main/webapp/assets/common/images/repo_fork.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
src/main/webapp/assets/common/images/repo_fork_lg.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
src/main/webapp/assets/common/images/repo_private.png
Normal file
|
After Width: | Height: | Size: 280 B |
BIN
src/main/webapp/assets/common/images/repo_private_lg.png
Normal file
|
After Width: | Height: | Size: 420 B |
BIN
src/main/webapp/assets/common/images/repo_public.png
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
src/main/webapp/assets/common/images/repo_public_lg.png
Normal file
|
After Width: | Height: | Size: 352 B |
@@ -11,6 +11,26 @@ $(function(){
|
|||||||
$('img[data-toggle=tooltip]').tooltip();
|
$('img[data-toggle=tooltip]').tooltip();
|
||||||
$('a[data-toggle=tooltip]').tooltip();
|
$('a[data-toggle=tooltip]').tooltip();
|
||||||
|
|
||||||
|
// anchor icon for markdown
|
||||||
|
$('.markdown-head').mouseenter(function(e){
|
||||||
|
$(e.target).children('a.markdown-anchor-link').show();
|
||||||
|
});
|
||||||
|
$('.markdown-head').mouseleave(function(e){
|
||||||
|
var anchorLink = $(e.target).children('a.markdown-anchor-link');
|
||||||
|
if(anchorLink.data('active') != true){
|
||||||
|
anchorLink.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('a.markdown-anchor-link').mouseenter(function(e){
|
||||||
|
$(e.target).data('active', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('a.markdown-anchor-link').mouseleave(function(e){
|
||||||
|
$(e.target).data('active', false);
|
||||||
|
$(e.target).hide();
|
||||||
|
});
|
||||||
|
|
||||||
// syntax highlighting by google-code-prettify
|
// syntax highlighting by google-code-prettify
|
||||||
prettyPrint();
|
prettyPrint();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ table.diff .replace {
|
|||||||
background-color:#FD8
|
background-color:#FD8
|
||||||
}
|
}
|
||||||
table.diff .delete {
|
table.diff .delete {
|
||||||
background-color:#E99;
|
background-color:#FFDDDD;
|
||||||
}
|
}
|
||||||
table.diff .skip {
|
table.diff .skip {
|
||||||
background-color:#EFEFEF;
|
background-color:#EFEFEF;
|
||||||
@@ -74,10 +74,10 @@ table.diff .skip {
|
|||||||
border-right:1px solid #BBC;
|
border-right:1px solid #BBC;
|
||||||
}
|
}
|
||||||
table.diff .insert {
|
table.diff .insert {
|
||||||
background-color:#9E9
|
background-color:#DDFFDD
|
||||||
}
|
}
|
||||||
table.diff th.author {
|
table.diff th.author {
|
||||||
text-align:right;
|
text-align:right;
|
||||||
border-top:1px solid #BBC;
|
border-top:1px solid #BBC;
|
||||||
background:#EFEFEF
|
background:#EFEFEF
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,22 @@ class StringUtilSpec extends Specification {
|
|||||||
StringUtil.sha1("abc") mustEqual "a9993e364706816aba3e25717850c26c9cd0d89d"
|
StringUtil.sha1("abc") mustEqual "a9993e364706816aba3e25717850c26c9cd0d89d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"extractIssueId" should {
|
||||||
|
"extract '#xxx' and return extracted id" in {
|
||||||
|
StringUtil.extractIssueId("(refs #123)").toSeq mustEqual Seq("123")
|
||||||
|
}
|
||||||
|
"returns Nil from message which does not contain #xxx" in {
|
||||||
|
StringUtil.extractIssueId("this is test!").toSeq mustEqual Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"extractCloseId" should {
|
||||||
|
"extract 'close #xxx' and return extracted id" in {
|
||||||
|
StringUtil.extractCloseId("(close #123)").toSeq mustEqual Seq("123")
|
||||||
|
}
|
||||||
|
"returns Nil from message which does not contain close command" in {
|
||||||
|
StringUtil.extractCloseId("(refs #123)").toSeq mustEqual Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import twirl.api.Html
|
|||||||
|
|
||||||
class AvatarImageProviderSpec extends Specification {
|
class AvatarImageProviderSpec extends Specification {
|
||||||
|
|
||||||
implicit val context = app.Context("", None, "", null)
|
implicit val context = app.Context("", None, null)
|
||||||
|
|
||||||
"getAvatarImageHtml" should {
|
"getAvatarImageHtml" should {
|
||||||
"show Gravatar image for no image account if gravatar integration is enabled" in {
|
"show Gravatar image for no image account if gravatar integration is enabled" in {
|
||||||
@@ -80,6 +80,7 @@ class AvatarImageProviderSpec extends Specification {
|
|||||||
|
|
||||||
private def createSystemSettings(useGravatar: Boolean) =
|
private def createSystemSettings(useGravatar: Boolean) =
|
||||||
SystemSettings(
|
SystemSettings(
|
||||||
|
baseUrl = None,
|
||||||
allowAccountRegistration = false,
|
allowAccountRegistration = false,
|
||||||
gravatar = useGravatar,
|
gravatar = useGravatar,
|
||||||
notification = false,
|
notification = false,
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ class GitBucketHtmlSerializerSpec extends Specification {
|
|||||||
"normalize characters with diacritics" in {
|
"normalize characters with diacritics" in {
|
||||||
val before = "Dónde estará mi vida"
|
val before = "Dónde estará mi vida"
|
||||||
val after = generateAnchorName(before)
|
val after = generateAnchorName(before)
|
||||||
after mustEqual "donde-estara-mi-vida"
|
after mustEqual "do%cc%81nde-estara%cc%81-mi-vida"
|
||||||
}
|
}
|
||||||
|
|
||||||
"omit special characters" in {
|
"omit special characters" in {
|
||||||
val before = "foo!bar@baz>9000"
|
val before = "foo!bar@baz>9000"
|
||||||
val after = generateAnchorName(before)
|
val after = generateAnchorName(before)
|
||||||
after mustEqual "foobarbaz9000"
|
after mustEqual "foo%21bar%40baz%3e9000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||