Fossil SCM
Merge with trunk to get the latest web UI fixes.
Commit
e317872280cbbb9f36bbd8099a7cb0f5beb28716c95be8de579ac01039eec1aa
Parent
cfed59c9d1fbe1c…
9 files changed
+44
-26
+34
-8
+55
-10
+1
-1
+25
-16
+1
-1
+1
-1
+130
-76
+54
-36
+44
-26
| --- Dockerfile | ||
| +++ Dockerfile | ||
| @@ -1,26 +1,44 @@ | ||
| 1 | -### | |
| 2 | -# Dockerfile for Fossil | |
| 3 | -### | |
| 4 | -FROM fedora:29 | |
| 5 | - | |
| 6 | -### Now install some additional parts we will need for the build | |
| 7 | -RUN dnf update -y && dnf install -y gcc make tcl tcl-devel zlib-devel openssl-devel tar && dnf clean all && groupadd -r fossil -g 433 && useradd -u 431 -r -g fossil -d /opt/fossil -s /sbin/nologin -c "Fossil user" fossil | |
| 8 | - | |
| 9 | -### If you want to build "trunk", change the next line accordingly. | |
| 10 | -ENV FOSSIL_INSTALL_VERSION release | |
| 11 | - | |
| 12 | -RUN curl "https://fossil-scm.org/home/tarball/fossil-src.tar.gz?name=fossil-src&uuid=${FOSSIL_INSTALL_VERSION}" | tar zx | |
| 13 | -RUN cd fossil-src && ./configure --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl=1 --with-tcl-stubs --with-tcl-private-stubs | |
| 14 | -RUN cd fossil-src/src && mv main.c main.c.orig && sed s/\"now\"/0/ <main.c.orig >main.c | |
| 15 | -RUN cd fossil-src && make && strip fossil && cp fossil /usr/bin && cd .. && rm -rf fossil-src && chmod a+rx /usr/bin/fossil && mkdir -p /opt/fossil && chown fossil:fossil /opt/fossil | |
| 16 | - | |
| 17 | -### Build is done, remove modules no longer needed | |
| 18 | -RUN dnf remove -y gcc make zlib-devel tcl-devel openssl-devel tar && dnf clean all | |
| 19 | - | |
| 20 | -USER fossil | |
| 21 | - | |
| 22 | -ENV HOME /opt/fossil | |
| 23 | - | |
| 24 | -EXPOSE 8080 | |
| 25 | - | |
| 26 | -CMD ["/usr/bin/fossil", "server", "--create", "--user", "admin", "/opt/fossil/repository.fossil"] | |
| 1 | +# STAGE 1: Build a static Fossil binary atop Alpine Linux | |
| 2 | + | |
| 3 | +# Avoid the temptation to swap the wget call below out for an ADD URL | |
| 4 | +# directive. The URL is fixed for a given release tag, which triggers | |
| 5 | +# Docker's caching behavior, causing it to reuse that version as long | |
| 6 | +# as it remains in the cache. We prefer to rely on the caching of the | |
| 7 | +# server instance on fossil-scm.org, which will keep these trunk | |
| 8 | +# tarballs around until the next trunk commit. | |
| 9 | + | |
| 10 | +FROM alpine:latest AS builder | |
| 11 | +WORKDIR /tmp | |
| 12 | +RUN apk update \ | |
| 13 | + && apk upgrade --no-cache \ | |
| 14 | + && apk add --no-cache \ | |
| 15 | + busybox-static gcc make \ | |
| 16 | + musl-dev \ | |
| 17 | + openssl-dev openssl-libs-static \ | |
| 18 | + zlib-dev zlib-static \ | |
| 19 | + && wget -O - https://fossil-scm.org/home/tarball/src | tar -xz \ | |
| 20 | + && src/configure --static CFLAGS='-Os -s' \ | |
| 21 | + && make -j | |
| 22 | + | |
| 23 | +# STAGE 2: Pare that back to the bare essentials. | |
| 24 | + | |
| 25 | +FROM scratch | |
| 26 | +WORKDIR /jail | |
| 27 | +COPY --from=builder /tmp/fossil /jail/bin/ | |
| 28 | +COPY --from=builder /bin/busybox.static /bin/busybox | |
| 29 | +RUN [ "/bin/busybox", "--install", "/bin" ] | |
| 30 | +RUN mkdir -m 700 dev museum \ | |
| 31 | + && mknod -m 600 dev/null c 1 3 \ | |
| 32 | + && mknod -m 600 dev/urandom c 1 9 | |
| 33 | + | |
| 34 | +# Now we can run the stripped-down environment in a chroot jail, while | |
| 35 | +# leaving open the option to debug it live via the Busybox shell. | |
| 36 | + | |
| 37 | +EXPOSE 8080/tcp | |
| 38 | +CMD [ \ | |
| 39 | + "bin/fossil", "server", \ | |
| 40 | + "--chroot", "/jail", \ | |
| 41 | + "--create", \ | |
| 42 | + "--jsmode", "bundled", \ | |
| 43 | + "--user", "admin", \ | |
| 44 | + "museum/repo.fossil"] | |
| 27 | 45 |
| --- Dockerfile | |
| +++ Dockerfile | |
| @@ -1,26 +1,44 @@ | |
| 1 | ### |
| 2 | # Dockerfile for Fossil |
| 3 | ### |
| 4 | FROM fedora:29 |
| 5 | |
| 6 | ### Now install some additional parts we will need for the build |
| 7 | RUN dnf update -y && dnf install -y gcc make tcl tcl-devel zlib-devel openssl-devel tar && dnf clean all && groupadd -r fossil -g 433 && useradd -u 431 -r -g fossil -d /opt/fossil -s /sbin/nologin -c "Fossil user" fossil |
| 8 | |
| 9 | ### If you want to build "trunk", change the next line accordingly. |
| 10 | ENV FOSSIL_INSTALL_VERSION release |
| 11 | |
| 12 | RUN curl "https://fossil-scm.org/home/tarball/fossil-src.tar.gz?name=fossil-src&uuid=${FOSSIL_INSTALL_VERSION}" | tar zx |
| 13 | RUN cd fossil-src && ./configure --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl=1 --with-tcl-stubs --with-tcl-private-stubs |
| 14 | RUN cd fossil-src/src && mv main.c main.c.orig && sed s/\"now\"/0/ <main.c.orig >main.c |
| 15 | RUN cd fossil-src && make && strip fossil && cp fossil /usr/bin && cd .. && rm -rf fossil-src && chmod a+rx /usr/bin/fossil && mkdir -p /opt/fossil && chown fossil:fossil /opt/fossil |
| 16 | |
| 17 | ### Build is done, remove modules no longer needed |
| 18 | RUN dnf remove -y gcc make zlib-devel tcl-devel openssl-devel tar && dnf clean all |
| 19 | |
| 20 | USER fossil |
| 21 | |
| 22 | ENV HOME /opt/fossil |
| 23 | |
| 24 | EXPOSE 8080 |
| 25 | |
| 26 | CMD ["/usr/bin/fossil", "server", "--create", "--user", "admin", "/opt/fossil/repository.fossil"] |
| 27 |
| --- Dockerfile | |
| +++ Dockerfile | |
| @@ -1,26 +1,44 @@ | |
| 1 | # STAGE 1: Build a static Fossil binary atop Alpine Linux |
| 2 | |
| 3 | # Avoid the temptation to swap the wget call below out for an ADD URL |
| 4 | # directive. The URL is fixed for a given release tag, which triggers |
| 5 | # Docker's caching behavior, causing it to reuse that version as long |
| 6 | # as it remains in the cache. We prefer to rely on the caching of the |
| 7 | # server instance on fossil-scm.org, which will keep these trunk |
| 8 | # tarballs around until the next trunk commit. |
| 9 | |
| 10 | FROM alpine:latest AS builder |
| 11 | WORKDIR /tmp |
| 12 | RUN apk update \ |
| 13 | && apk upgrade --no-cache \ |
| 14 | && apk add --no-cache \ |
| 15 | busybox-static gcc make \ |
| 16 | musl-dev \ |
| 17 | openssl-dev openssl-libs-static \ |
| 18 | zlib-dev zlib-static \ |
| 19 | && wget -O - https://fossil-scm.org/home/tarball/src | tar -xz \ |
| 20 | && src/configure --static CFLAGS='-Os -s' \ |
| 21 | && make -j |
| 22 | |
| 23 | # STAGE 2: Pare that back to the bare essentials. |
| 24 | |
| 25 | FROM scratch |
| 26 | WORKDIR /jail |
| 27 | COPY --from=builder /tmp/fossil /jail/bin/ |
| 28 | COPY --from=builder /bin/busybox.static /bin/busybox |
| 29 | RUN [ "/bin/busybox", "--install", "/bin" ] |
| 30 | RUN mkdir -m 700 dev museum \ |
| 31 | && mknod -m 600 dev/null c 1 3 \ |
| 32 | && mknod -m 600 dev/urandom c 1 9 |
| 33 | |
| 34 | # Now we can run the stripped-down environment in a chroot jail, while |
| 35 | # leaving open the option to debug it live via the Busybox shell. |
| 36 | |
| 37 | EXPOSE 8080/tcp |
| 38 | CMD [ \ |
| 39 | "bin/fossil", "server", \ |
| 40 | "--chroot", "/jail", \ |
| 41 | "--create", \ |
| 42 | "--jsmode", "bundled", \ |
| 43 | "--user", "admin", \ |
| 44 | "museum/repo.fossil"] |
| 45 |
+34
-8
| --- src/accordion.js | ||
| +++ src/accordion.js | ||
| @@ -1,12 +1,34 @@ | ||
| 1 | -/* Attach appropriate javascript to each ".accordion" button so that | |
| 2 | -** it expands and contracts when clicked. | |
| 3 | -** The uncompressed source code for the SVG icons can be found on the | |
| 4 | -** wiki page "branch/accordion-experiments" in the Fossil repository. | |
| 1 | +/* | |
| 2 | +** Attach appropriate javascript to each ".accordion" button so that it expands | |
| 3 | +** and contracts when clicked. | |
| 4 | +** | |
| 5 | +** The uncompressed source code for the SVG icons can be found on the wiki page | |
| 6 | +** "branch/accordion-experiments" in the Fossil repository. | |
| 7 | +** | |
| 8 | +** Implementation notes: | |
| 9 | +** | |
| 10 | +** The `maxHeight' CSS property is quite restrictive for vertical resizing of | |
| 11 | +** elements, especially for dynamic-content areas like the diff panels. That's | |
| 12 | +** why `maxHeight' is set only during animation, to prevent truncated elements. | |
| 13 | +** (The diff panels may get truncated right after page loading, and other | |
| 14 | +** elements may get truncated when resizing the browser window to a smaller | |
| 15 | +** width, causing vertical growth.) | |
| 16 | +** | |
| 17 | +** Another problem is that `scrollHeight' used to calculate the expanded height | |
| 18 | +** while still in the contracted state may return values with small errors on | |
| 19 | +** some browsers, especially for large elements, presumably due to omitting the | |
| 20 | +** space required by the vertical scrollbar that may become necessary, causing | |
| 21 | +** additional horizontal shrinking and consequently more vertical growth than | |
| 22 | +** calculated. That's why setting `maxHeight' to `scrollHeight' is considered | |
| 23 | +** "good enough" only during animation, but cleared afterwards. | |
| 24 | +** | |
| 25 | +** https://fossil-scm.org/forum/forumpost/66d7075f40 | |
| 26 | +** https://fossil-scm.org/home/timeline?r=accordion-fix | |
| 5 | 27 | */ |
| 6 | 28 | var acc_svgdata = ["data:image/svg+xml,"+ |
| 7 | - "%3Csvg xmlns='http:"+"/"+"/www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+ | |
| 29 | + "%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+ | |
| 8 | 30 | "%3Cpath style='fill:black;opacity:0' d='M16,16H0V0h16v16z'/%3E"+ |
| 9 | 31 | "%3Cpath style='fill:rgb(240,240,240)' d='M14,14H2V2h12v12z'/%3E"+ |
| 10 | 32 | "%3Cpath style='fill:rgb(64,64,64)' d='M13,13H3V3h10v10z'/%3E"+ |
| 11 | 33 | "%3Cpath style='fill:rgb(248,248,248)' d='M12,12H4V4h8v8z'/%3E"+ |
| 12 | 34 | "%3Cpath style='fill:rgb(80,128,208)' d='", "'/%3E%3C/svg%3E", |
| @@ -19,17 +41,21 @@ | ||
| 19 | 41 | a[i].insertBefore(img,a[i].firstChild); |
| 20 | 42 | img = document.createElement("img"); |
| 21 | 43 | img.src = acc_svgdata[0]+acc_svgdata[3]+acc_svgdata[1]; |
| 22 | 44 | img.className = "accordion_btn accordion_btn_minus"; |
| 23 | 45 | a[i].insertBefore(img,a[i].firstChild); |
| 24 | - var p = a[i].nextElementSibling; | |
| 25 | - p.style.maxHeight = p.scrollHeight + "px"; | |
| 26 | 46 | a[i].addEventListener("click",function(){ |
| 27 | 47 | var x = this.nextElementSibling; |
| 28 | 48 | if( this.classList.contains("accordion_closed") ){ |
| 29 | 49 | x.style.maxHeight = x.scrollHeight + "px"; |
| 50 | + setTimeout(function(){ | |
| 51 | + x.style.maxHeight = ""; | |
| 52 | + },250); // default.css: .accordion_panel { transition-duration } | |
| 30 | 53 | }else{ |
| 31 | - x.style.maxHeight = "0"; | |
| 54 | + x.style.maxHeight = x.scrollHeight + "px"; | |
| 55 | + setTimeout(function(){ | |
| 56 | + x.style.maxHeight = "0"; | |
| 57 | + },1); | |
| 32 | 58 | } |
| 33 | 59 | this.classList.toggle("accordion_closed"); |
| 34 | 60 | }); |
| 35 | 61 | } |
| 36 | 62 |
| --- src/accordion.js | |
| +++ src/accordion.js | |
| @@ -1,12 +1,34 @@ | |
| 1 | /* Attach appropriate javascript to each ".accordion" button so that |
| 2 | ** it expands and contracts when clicked. |
| 3 | ** The uncompressed source code for the SVG icons can be found on the |
| 4 | ** wiki page "branch/accordion-experiments" in the Fossil repository. |
| 5 | */ |
| 6 | var acc_svgdata = ["data:image/svg+xml,"+ |
| 7 | "%3Csvg xmlns='http:"+"/"+"/www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+ |
| 8 | "%3Cpath style='fill:black;opacity:0' d='M16,16H0V0h16v16z'/%3E"+ |
| 9 | "%3Cpath style='fill:rgb(240,240,240)' d='M14,14H2V2h12v12z'/%3E"+ |
| 10 | "%3Cpath style='fill:rgb(64,64,64)' d='M13,13H3V3h10v10z'/%3E"+ |
| 11 | "%3Cpath style='fill:rgb(248,248,248)' d='M12,12H4V4h8v8z'/%3E"+ |
| 12 | "%3Cpath style='fill:rgb(80,128,208)' d='", "'/%3E%3C/svg%3E", |
| @@ -19,17 +41,21 @@ | |
| 19 | a[i].insertBefore(img,a[i].firstChild); |
| 20 | img = document.createElement("img"); |
| 21 | img.src = acc_svgdata[0]+acc_svgdata[3]+acc_svgdata[1]; |
| 22 | img.className = "accordion_btn accordion_btn_minus"; |
| 23 | a[i].insertBefore(img,a[i].firstChild); |
| 24 | var p = a[i].nextElementSibling; |
| 25 | p.style.maxHeight = p.scrollHeight + "px"; |
| 26 | a[i].addEventListener("click",function(){ |
| 27 | var x = this.nextElementSibling; |
| 28 | if( this.classList.contains("accordion_closed") ){ |
| 29 | x.style.maxHeight = x.scrollHeight + "px"; |
| 30 | }else{ |
| 31 | x.style.maxHeight = "0"; |
| 32 | } |
| 33 | this.classList.toggle("accordion_closed"); |
| 34 | }); |
| 35 | } |
| 36 |
| --- src/accordion.js | |
| +++ src/accordion.js | |
| @@ -1,12 +1,34 @@ | |
| 1 | /* |
| 2 | ** Attach appropriate javascript to each ".accordion" button so that it expands |
| 3 | ** and contracts when clicked. |
| 4 | ** |
| 5 | ** The uncompressed source code for the SVG icons can be found on the wiki page |
| 6 | ** "branch/accordion-experiments" in the Fossil repository. |
| 7 | ** |
| 8 | ** Implementation notes: |
| 9 | ** |
| 10 | ** The `maxHeight' CSS property is quite restrictive for vertical resizing of |
| 11 | ** elements, especially for dynamic-content areas like the diff panels. That's |
| 12 | ** why `maxHeight' is set only during animation, to prevent truncated elements. |
| 13 | ** (The diff panels may get truncated right after page loading, and other |
| 14 | ** elements may get truncated when resizing the browser window to a smaller |
| 15 | ** width, causing vertical growth.) |
| 16 | ** |
| 17 | ** Another problem is that `scrollHeight' used to calculate the expanded height |
| 18 | ** while still in the contracted state may return values with small errors on |
| 19 | ** some browsers, especially for large elements, presumably due to omitting the |
| 20 | ** space required by the vertical scrollbar that may become necessary, causing |
| 21 | ** additional horizontal shrinking and consequently more vertical growth than |
| 22 | ** calculated. That's why setting `maxHeight' to `scrollHeight' is considered |
| 23 | ** "good enough" only during animation, but cleared afterwards. |
| 24 | ** |
| 25 | ** https://fossil-scm.org/forum/forumpost/66d7075f40 |
| 26 | ** https://fossil-scm.org/home/timeline?r=accordion-fix |
| 27 | */ |
| 28 | var acc_svgdata = ["data:image/svg+xml,"+ |
| 29 | "%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+ |
| 30 | "%3Cpath style='fill:black;opacity:0' d='M16,16H0V0h16v16z'/%3E"+ |
| 31 | "%3Cpath style='fill:rgb(240,240,240)' d='M14,14H2V2h12v12z'/%3E"+ |
| 32 | "%3Cpath style='fill:rgb(64,64,64)' d='M13,13H3V3h10v10z'/%3E"+ |
| 33 | "%3Cpath style='fill:rgb(248,248,248)' d='M12,12H4V4h8v8z'/%3E"+ |
| 34 | "%3Cpath style='fill:rgb(80,128,208)' d='", "'/%3E%3C/svg%3E", |
| @@ -19,17 +41,21 @@ | |
| 41 | a[i].insertBefore(img,a[i].firstChild); |
| 42 | img = document.createElement("img"); |
| 43 | img.src = acc_svgdata[0]+acc_svgdata[3]+acc_svgdata[1]; |
| 44 | img.className = "accordion_btn accordion_btn_minus"; |
| 45 | a[i].insertBefore(img,a[i].firstChild); |
| 46 | a[i].addEventListener("click",function(){ |
| 47 | var x = this.nextElementSibling; |
| 48 | if( this.classList.contains("accordion_closed") ){ |
| 49 | x.style.maxHeight = x.scrollHeight + "px"; |
| 50 | setTimeout(function(){ |
| 51 | x.style.maxHeight = ""; |
| 52 | },250); // default.css: .accordion_panel { transition-duration } |
| 53 | }else{ |
| 54 | x.style.maxHeight = x.scrollHeight + "px"; |
| 55 | setTimeout(function(){ |
| 56 | x.style.maxHeight = "0"; |
| 57 | },1); |
| 58 | } |
| 59 | this.classList.toggle("accordion_closed"); |
| 60 | }); |
| 61 | } |
| 62 |
+55
-10
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -289,31 +289,51 @@ | ||
| 289 | 289 | ** Prepare a query that will list branches. |
| 290 | 290 | ** |
| 291 | 291 | ** If (which<0) then the query pulls only closed branches. If |
| 292 | 292 | ** (which>0) then the query pulls all (closed and opened) |
| 293 | 293 | ** branches. Else the query pulls currently-opened branches. |
| 294 | +** | |
| 295 | +** If the BRL_ORDERBY_MTIME flag is set and nLimitMRU ("Limit Most Recently Used | |
| 296 | +** style") is a non-zero number, the result is limited to nLimitMRU entries, and | |
| 297 | +** the BRL_REVERSE flag is applied in an outer query after processing the limit, | |
| 298 | +** so that it's possible to generate short lists with the most recently modified | |
| 299 | +** branches sorted chronologically in either direction, as does the "branch lsh" | |
| 300 | +** command. | |
| 301 | +** For other cases, the outer query is also generated, but works as a no-op. The | |
| 302 | +** code to build the outer query is marked with *//* OUTER QUERY *//* comments. | |
| 294 | 303 | */ |
| 295 | -void branch_prepare_list_query(Stmt *pQuery, int brFlags, const char *zBrNameGlob){ | |
| 304 | +void branch_prepare_list_query( | |
| 305 | + Stmt *pQuery, | |
| 306 | + int brFlags, | |
| 307 | + const char *zBrNameGlob, | |
| 308 | + int nLimitMRU | |
| 309 | +){ | |
| 296 | 310 | Blob sql; |
| 297 | 311 | blob_init(&sql, 0, 0); |
| 298 | 312 | brlist_create_temp_table(); |
| 313 | + /* Ignore nLimitMRU if no chronological sort requested. */ | |
| 314 | + if( (brFlags & BRL_ORDERBY_MTIME)==0 ) nLimitMRU = 0; | |
| 315 | + /* Undocumented: invert negative values for nLimitMRU, so that command-line | |
| 316 | + ** arguments similar to `head -5' with "option numbers" are possible. */ | |
| 317 | + if( nLimitMRU<0 ) nLimitMRU = -nLimitMRU; | |
| 318 | + blob_append_sql(&sql,"SELECT name, isprivate FROM ("); /* OUTER QUERY */ | |
| 299 | 319 | switch( brFlags & BRL_OPEN_CLOSED_MASK ){ |
| 300 | 320 | case BRL_CLOSED_ONLY: { |
| 301 | 321 | blob_append_sql(&sql, |
| 302 | - "SELECT name, isprivate FROM tmp_brlist WHERE isclosed" | |
| 322 | + "SELECT name, isprivate, mtime FROM tmp_brlist WHERE isclosed" | |
| 303 | 323 | ); |
| 304 | 324 | break; |
| 305 | 325 | } |
| 306 | 326 | case BRL_BOTH: { |
| 307 | 327 | blob_append_sql(&sql, |
| 308 | - "SELECT name, isprivate FROM tmp_brlist WHERE 1" | |
| 328 | + "SELECT name, isprivate, mtime FROM tmp_brlist WHERE 1" | |
| 309 | 329 | ); |
| 310 | 330 | break; |
| 311 | 331 | } |
| 312 | 332 | case BRL_OPEN_ONLY: { |
| 313 | 333 | blob_append_sql(&sql, |
| 314 | - "SELECT name, isprivate FROM tmp_brlist WHERE NOT isclosed" | |
| 334 | + "SELECT name, isprivate, mtime FROM tmp_brlist WHERE NOT isclosed" | |
| 315 | 335 | ); |
| 316 | 336 | break; |
| 317 | 337 | } |
| 318 | 338 | } |
| 319 | 339 | if( brFlags & BRL_PRIVATE ) blob_append_sql(&sql, " AND isprivate"); |
| @@ -321,13 +341,20 @@ | ||
| 321 | 341 | if( brFlags & BRL_ORDERBY_MTIME ){ |
| 322 | 342 | blob_append_sql(&sql, " ORDER BY -mtime"); |
| 323 | 343 | }else{ |
| 324 | 344 | blob_append_sql(&sql, " ORDER BY name COLLATE nocase"); |
| 325 | 345 | } |
| 326 | - if( brFlags & BRL_REVERSE ){ | |
| 346 | + if( brFlags & BRL_REVERSE && !nLimitMRU ){ | |
| 327 | 347 | blob_append_sql(&sql," DESC"); |
| 328 | 348 | } |
| 349 | + if( nLimitMRU ){ | |
| 350 | + blob_append_sql(&sql," LIMIT %d",nLimitMRU); | |
| 351 | + } | |
| 352 | + blob_append_sql(&sql,")"); /* OUTER QUERY */ | |
| 353 | + if( brFlags & BRL_REVERSE && nLimitMRU ){ | |
| 354 | + blob_append_sql(&sql," ORDER BY mtime"); /* OUTER QUERY */ | |
| 355 | + } | |
| 329 | 356 | db_prepare_blob(pQuery, &sql); |
| 330 | 357 | blob_reset(&sql); |
| 331 | 358 | } |
| 332 | 359 | |
| 333 | 360 | /* |
| @@ -585,10 +612,11 @@ | ||
| 585 | 612 | ** > fossil branch info BRANCH-NAME |
| 586 | 613 | ** |
| 587 | 614 | ** Print information about a branch |
| 588 | 615 | ** |
| 589 | 616 | ** > fossil branch list|ls ?OPTIONS? ?GLOB? |
| 617 | +** > fossil branch lsh ?OPTIONS? ?LIMIT? | |
| 590 | 618 | ** |
| 591 | 619 | ** List all branches. Options: |
| 592 | 620 | ** -a|--all List all branches. Default show only open branches |
| 593 | 621 | ** -c|--closed List closed branches. |
| 594 | 622 | ** -p List only private branches. |
| @@ -597,10 +625,15 @@ | ||
| 597 | 625 | ** |
| 598 | 626 | ** The current branch is marked with an asterisk. Private branches are |
| 599 | 627 | ** marked with a hash sign. |
| 600 | 628 | ** |
| 601 | 629 | ** If GLOB is given, show only branches matching the pattern. |
| 630 | +** | |
| 631 | +** The "lsh" variant of this subcommand shows recently changed branches, | |
| 632 | +** and accepts an optional LIMIT argument (defaults to 5) to cap output, | |
| 633 | +** but no GLOB argument. All other options are supported, with -t being | |
| 634 | +** an implied no-op. | |
| 602 | 635 | ** |
| 603 | 636 | ** > fossil branch new BRANCH-NAME BASIS ?OPTIONS? |
| 604 | 637 | ** |
| 605 | 638 | ** Create a new branch BRANCH-NAME off of check-in BASIS. |
| 606 | 639 | ** Supported options for this subcommand include: |
| @@ -651,29 +684,41 @@ | ||
| 651 | 684 | "SELECT datetime(mtime,toLocal()) FROM event" |
| 652 | 685 | " WHERE objid=%d", rid); |
| 653 | 686 | fossil_print("%s: open as of %s on %.16s\n", zBrName, zDate, zUuid); |
| 654 | 687 | } |
| 655 | 688 | } |
| 656 | - }else if( (strncmp(zCmd,"list",n)==0)||(strncmp(zCmd, "ls", n)==0) ){ | |
| 689 | + }else if( strncmp(zCmd,"list",n)==0 || | |
| 690 | + strncmp(zCmd, "ls", n)==0 || | |
| 691 | + strcmp(zCmd, "lsh")==0 ){ | |
| 657 | 692 | Stmt q; |
| 658 | 693 | int vid; |
| 659 | 694 | char *zCurrent = 0; |
| 660 | 695 | const char *zBrNameGlob = 0; |
| 696 | + int nLimit = 0; | |
| 661 | 697 | int brFlags = BRL_OPEN_ONLY; |
| 662 | 698 | if( find_option("all","a",0)!=0 ) brFlags = BRL_BOTH; |
| 663 | 699 | if( find_option("closed","c",0)!=0 ) brFlags = BRL_CLOSED_ONLY; |
| 664 | 700 | if( find_option("t",0,0)!=0 ) brFlags |= BRL_ORDERBY_MTIME; |
| 665 | 701 | if( find_option("r",0,0)!=0 ) brFlags |= BRL_REVERSE; |
| 666 | 702 | if( find_option("p",0,0)!=0 ) brFlags |= BRL_PRIVATE; |
| 667 | - if( g.argc >= 4 ) zBrNameGlob = g.argv[3]; | |
| 703 | + | |
| 704 | + if( strcmp(zCmd, "lsh")==0 ){ | |
| 705 | + nLimit = 5; | |
| 706 | + if( g.argc>4 || (g.argc==4 && (nLimit = atoi(g.argv[3]))==0) ){ | |
| 707 | + fossil_fatal("the lsh subcommand allows one optional numeric argument"); | |
| 708 | + } | |
| 709 | + brFlags |= BRL_ORDERBY_MTIME; | |
| 710 | + }else{ | |
| 711 | + if( g.argc >= 4 ) zBrNameGlob = g.argv[3]; | |
| 712 | + } | |
| 668 | 713 | |
| 669 | 714 | if( g.localOpen ){ |
| 670 | 715 | vid = db_lget_int("checkout", 0); |
| 671 | 716 | zCurrent = db_text(0, "SELECT value FROM tagxref" |
| 672 | 717 | " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); |
| 673 | 718 | } |
| 674 | - branch_prepare_list_query(&q, brFlags, zBrNameGlob); | |
| 719 | + branch_prepare_list_query(&q, brFlags, zBrNameGlob, nLimit); | |
| 675 | 720 | while( db_step(&q)==SQLITE_ROW ){ |
| 676 | 721 | const char *zBr = db_column_text(&q, 0); |
| 677 | 722 | int isPriv = zCurrent!=0 && db_column_int(&q, 1)==1; |
| 678 | 723 | int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0; |
| 679 | 724 | fossil_print("%s%s%s\n", |
| @@ -703,11 +748,11 @@ | ||
| 703 | 748 | usage("branch unhide branch-name(s)..."); |
| 704 | 749 | } |
| 705 | 750 | branch_cmd_hide(3,0); |
| 706 | 751 | }else{ |
| 707 | 752 | fossil_fatal("branch subcommand should be one of: " |
| 708 | - "close current hide info list ls new reopen unhide"); | |
| 753 | + "close current hide info list ls lsh new reopen unhide"); | |
| 709 | 754 | } |
| 710 | 755 | } |
| 711 | 756 | |
| 712 | 757 | /* |
| 713 | 758 | ** This is the new-style branch-list page that shows the branch names |
| @@ -862,11 +907,11 @@ | ||
| 862 | 907 | @ reopened).</li> |
| 863 | 908 | @ </ol> |
| 864 | 909 | style_sidebox_end(); |
| 865 | 910 | #endif |
| 866 | 911 | |
| 867 | - branch_prepare_list_query(&q, brFlags, 0); | |
| 912 | + branch_prepare_list_query(&q, brFlags, 0, 0); | |
| 868 | 913 | cnt = 0; |
| 869 | 914 | while( db_step(&q)==SQLITE_ROW ){ |
| 870 | 915 | const char *zBr = db_column_text(&q, 0); |
| 871 | 916 | if( cnt==0 ){ |
| 872 | 917 | if( colorTest ){ |
| 873 | 918 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -289,31 +289,51 @@ | |
| 289 | ** Prepare a query that will list branches. |
| 290 | ** |
| 291 | ** If (which<0) then the query pulls only closed branches. If |
| 292 | ** (which>0) then the query pulls all (closed and opened) |
| 293 | ** branches. Else the query pulls currently-opened branches. |
| 294 | */ |
| 295 | void branch_prepare_list_query(Stmt *pQuery, int brFlags, const char *zBrNameGlob){ |
| 296 | Blob sql; |
| 297 | blob_init(&sql, 0, 0); |
| 298 | brlist_create_temp_table(); |
| 299 | switch( brFlags & BRL_OPEN_CLOSED_MASK ){ |
| 300 | case BRL_CLOSED_ONLY: { |
| 301 | blob_append_sql(&sql, |
| 302 | "SELECT name, isprivate FROM tmp_brlist WHERE isclosed" |
| 303 | ); |
| 304 | break; |
| 305 | } |
| 306 | case BRL_BOTH: { |
| 307 | blob_append_sql(&sql, |
| 308 | "SELECT name, isprivate FROM tmp_brlist WHERE 1" |
| 309 | ); |
| 310 | break; |
| 311 | } |
| 312 | case BRL_OPEN_ONLY: { |
| 313 | blob_append_sql(&sql, |
| 314 | "SELECT name, isprivate FROM tmp_brlist WHERE NOT isclosed" |
| 315 | ); |
| 316 | break; |
| 317 | } |
| 318 | } |
| 319 | if( brFlags & BRL_PRIVATE ) blob_append_sql(&sql, " AND isprivate"); |
| @@ -321,13 +341,20 @@ | |
| 321 | if( brFlags & BRL_ORDERBY_MTIME ){ |
| 322 | blob_append_sql(&sql, " ORDER BY -mtime"); |
| 323 | }else{ |
| 324 | blob_append_sql(&sql, " ORDER BY name COLLATE nocase"); |
| 325 | } |
| 326 | if( brFlags & BRL_REVERSE ){ |
| 327 | blob_append_sql(&sql," DESC"); |
| 328 | } |
| 329 | db_prepare_blob(pQuery, &sql); |
| 330 | blob_reset(&sql); |
| 331 | } |
| 332 | |
| 333 | /* |
| @@ -585,10 +612,11 @@ | |
| 585 | ** > fossil branch info BRANCH-NAME |
| 586 | ** |
| 587 | ** Print information about a branch |
| 588 | ** |
| 589 | ** > fossil branch list|ls ?OPTIONS? ?GLOB? |
| 590 | ** |
| 591 | ** List all branches. Options: |
| 592 | ** -a|--all List all branches. Default show only open branches |
| 593 | ** -c|--closed List closed branches. |
| 594 | ** -p List only private branches. |
| @@ -597,10 +625,15 @@ | |
| 597 | ** |
| 598 | ** The current branch is marked with an asterisk. Private branches are |
| 599 | ** marked with a hash sign. |
| 600 | ** |
| 601 | ** If GLOB is given, show only branches matching the pattern. |
| 602 | ** |
| 603 | ** > fossil branch new BRANCH-NAME BASIS ?OPTIONS? |
| 604 | ** |
| 605 | ** Create a new branch BRANCH-NAME off of check-in BASIS. |
| 606 | ** Supported options for this subcommand include: |
| @@ -651,29 +684,41 @@ | |
| 651 | "SELECT datetime(mtime,toLocal()) FROM event" |
| 652 | " WHERE objid=%d", rid); |
| 653 | fossil_print("%s: open as of %s on %.16s\n", zBrName, zDate, zUuid); |
| 654 | } |
| 655 | } |
| 656 | }else if( (strncmp(zCmd,"list",n)==0)||(strncmp(zCmd, "ls", n)==0) ){ |
| 657 | Stmt q; |
| 658 | int vid; |
| 659 | char *zCurrent = 0; |
| 660 | const char *zBrNameGlob = 0; |
| 661 | int brFlags = BRL_OPEN_ONLY; |
| 662 | if( find_option("all","a",0)!=0 ) brFlags = BRL_BOTH; |
| 663 | if( find_option("closed","c",0)!=0 ) brFlags = BRL_CLOSED_ONLY; |
| 664 | if( find_option("t",0,0)!=0 ) brFlags |= BRL_ORDERBY_MTIME; |
| 665 | if( find_option("r",0,0)!=0 ) brFlags |= BRL_REVERSE; |
| 666 | if( find_option("p",0,0)!=0 ) brFlags |= BRL_PRIVATE; |
| 667 | if( g.argc >= 4 ) zBrNameGlob = g.argv[3]; |
| 668 | |
| 669 | if( g.localOpen ){ |
| 670 | vid = db_lget_int("checkout", 0); |
| 671 | zCurrent = db_text(0, "SELECT value FROM tagxref" |
| 672 | " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); |
| 673 | } |
| 674 | branch_prepare_list_query(&q, brFlags, zBrNameGlob); |
| 675 | while( db_step(&q)==SQLITE_ROW ){ |
| 676 | const char *zBr = db_column_text(&q, 0); |
| 677 | int isPriv = zCurrent!=0 && db_column_int(&q, 1)==1; |
| 678 | int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0; |
| 679 | fossil_print("%s%s%s\n", |
| @@ -703,11 +748,11 @@ | |
| 703 | usage("branch unhide branch-name(s)..."); |
| 704 | } |
| 705 | branch_cmd_hide(3,0); |
| 706 | }else{ |
| 707 | fossil_fatal("branch subcommand should be one of: " |
| 708 | "close current hide info list ls new reopen unhide"); |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | /* |
| 713 | ** This is the new-style branch-list page that shows the branch names |
| @@ -862,11 +907,11 @@ | |
| 862 | @ reopened).</li> |
| 863 | @ </ol> |
| 864 | style_sidebox_end(); |
| 865 | #endif |
| 866 | |
| 867 | branch_prepare_list_query(&q, brFlags, 0); |
| 868 | cnt = 0; |
| 869 | while( db_step(&q)==SQLITE_ROW ){ |
| 870 | const char *zBr = db_column_text(&q, 0); |
| 871 | if( cnt==0 ){ |
| 872 | if( colorTest ){ |
| 873 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -289,31 +289,51 @@ | |
| 289 | ** Prepare a query that will list branches. |
| 290 | ** |
| 291 | ** If (which<0) then the query pulls only closed branches. If |
| 292 | ** (which>0) then the query pulls all (closed and opened) |
| 293 | ** branches. Else the query pulls currently-opened branches. |
| 294 | ** |
| 295 | ** If the BRL_ORDERBY_MTIME flag is set and nLimitMRU ("Limit Most Recently Used |
| 296 | ** style") is a non-zero number, the result is limited to nLimitMRU entries, and |
| 297 | ** the BRL_REVERSE flag is applied in an outer query after processing the limit, |
| 298 | ** so that it's possible to generate short lists with the most recently modified |
| 299 | ** branches sorted chronologically in either direction, as does the "branch lsh" |
| 300 | ** command. |
| 301 | ** For other cases, the outer query is also generated, but works as a no-op. The |
| 302 | ** code to build the outer query is marked with *//* OUTER QUERY *//* comments. |
| 303 | */ |
| 304 | void branch_prepare_list_query( |
| 305 | Stmt *pQuery, |
| 306 | int brFlags, |
| 307 | const char *zBrNameGlob, |
| 308 | int nLimitMRU |
| 309 | ){ |
| 310 | Blob sql; |
| 311 | blob_init(&sql, 0, 0); |
| 312 | brlist_create_temp_table(); |
| 313 | /* Ignore nLimitMRU if no chronological sort requested. */ |
| 314 | if( (brFlags & BRL_ORDERBY_MTIME)==0 ) nLimitMRU = 0; |
| 315 | /* Undocumented: invert negative values for nLimitMRU, so that command-line |
| 316 | ** arguments similar to `head -5' with "option numbers" are possible. */ |
| 317 | if( nLimitMRU<0 ) nLimitMRU = -nLimitMRU; |
| 318 | blob_append_sql(&sql,"SELECT name, isprivate FROM ("); /* OUTER QUERY */ |
| 319 | switch( brFlags & BRL_OPEN_CLOSED_MASK ){ |
| 320 | case BRL_CLOSED_ONLY: { |
| 321 | blob_append_sql(&sql, |
| 322 | "SELECT name, isprivate, mtime FROM tmp_brlist WHERE isclosed" |
| 323 | ); |
| 324 | break; |
| 325 | } |
| 326 | case BRL_BOTH: { |
| 327 | blob_append_sql(&sql, |
| 328 | "SELECT name, isprivate, mtime FROM tmp_brlist WHERE 1" |
| 329 | ); |
| 330 | break; |
| 331 | } |
| 332 | case BRL_OPEN_ONLY: { |
| 333 | blob_append_sql(&sql, |
| 334 | "SELECT name, isprivate, mtime FROM tmp_brlist WHERE NOT isclosed" |
| 335 | ); |
| 336 | break; |
| 337 | } |
| 338 | } |
| 339 | if( brFlags & BRL_PRIVATE ) blob_append_sql(&sql, " AND isprivate"); |
| @@ -321,13 +341,20 @@ | |
| 341 | if( brFlags & BRL_ORDERBY_MTIME ){ |
| 342 | blob_append_sql(&sql, " ORDER BY -mtime"); |
| 343 | }else{ |
| 344 | blob_append_sql(&sql, " ORDER BY name COLLATE nocase"); |
| 345 | } |
| 346 | if( brFlags & BRL_REVERSE && !nLimitMRU ){ |
| 347 | blob_append_sql(&sql," DESC"); |
| 348 | } |
| 349 | if( nLimitMRU ){ |
| 350 | blob_append_sql(&sql," LIMIT %d",nLimitMRU); |
| 351 | } |
| 352 | blob_append_sql(&sql,")"); /* OUTER QUERY */ |
| 353 | if( brFlags & BRL_REVERSE && nLimitMRU ){ |
| 354 | blob_append_sql(&sql," ORDER BY mtime"); /* OUTER QUERY */ |
| 355 | } |
| 356 | db_prepare_blob(pQuery, &sql); |
| 357 | blob_reset(&sql); |
| 358 | } |
| 359 | |
| 360 | /* |
| @@ -585,10 +612,11 @@ | |
| 612 | ** > fossil branch info BRANCH-NAME |
| 613 | ** |
| 614 | ** Print information about a branch |
| 615 | ** |
| 616 | ** > fossil branch list|ls ?OPTIONS? ?GLOB? |
| 617 | ** > fossil branch lsh ?OPTIONS? ?LIMIT? |
| 618 | ** |
| 619 | ** List all branches. Options: |
| 620 | ** -a|--all List all branches. Default show only open branches |
| 621 | ** -c|--closed List closed branches. |
| 622 | ** -p List only private branches. |
| @@ -597,10 +625,15 @@ | |
| 625 | ** |
| 626 | ** The current branch is marked with an asterisk. Private branches are |
| 627 | ** marked with a hash sign. |
| 628 | ** |
| 629 | ** If GLOB is given, show only branches matching the pattern. |
| 630 | ** |
| 631 | ** The "lsh" variant of this subcommand shows recently changed branches, |
| 632 | ** and accepts an optional LIMIT argument (defaults to 5) to cap output, |
| 633 | ** but no GLOB argument. All other options are supported, with -t being |
| 634 | ** an implied no-op. |
| 635 | ** |
| 636 | ** > fossil branch new BRANCH-NAME BASIS ?OPTIONS? |
| 637 | ** |
| 638 | ** Create a new branch BRANCH-NAME off of check-in BASIS. |
| 639 | ** Supported options for this subcommand include: |
| @@ -651,29 +684,41 @@ | |
| 684 | "SELECT datetime(mtime,toLocal()) FROM event" |
| 685 | " WHERE objid=%d", rid); |
| 686 | fossil_print("%s: open as of %s on %.16s\n", zBrName, zDate, zUuid); |
| 687 | } |
| 688 | } |
| 689 | }else if( strncmp(zCmd,"list",n)==0 || |
| 690 | strncmp(zCmd, "ls", n)==0 || |
| 691 | strcmp(zCmd, "lsh")==0 ){ |
| 692 | Stmt q; |
| 693 | int vid; |
| 694 | char *zCurrent = 0; |
| 695 | const char *zBrNameGlob = 0; |
| 696 | int nLimit = 0; |
| 697 | int brFlags = BRL_OPEN_ONLY; |
| 698 | if( find_option("all","a",0)!=0 ) brFlags = BRL_BOTH; |
| 699 | if( find_option("closed","c",0)!=0 ) brFlags = BRL_CLOSED_ONLY; |
| 700 | if( find_option("t",0,0)!=0 ) brFlags |= BRL_ORDERBY_MTIME; |
| 701 | if( find_option("r",0,0)!=0 ) brFlags |= BRL_REVERSE; |
| 702 | if( find_option("p",0,0)!=0 ) brFlags |= BRL_PRIVATE; |
| 703 | |
| 704 | if( strcmp(zCmd, "lsh")==0 ){ |
| 705 | nLimit = 5; |
| 706 | if( g.argc>4 || (g.argc==4 && (nLimit = atoi(g.argv[3]))==0) ){ |
| 707 | fossil_fatal("the lsh subcommand allows one optional numeric argument"); |
| 708 | } |
| 709 | brFlags |= BRL_ORDERBY_MTIME; |
| 710 | }else{ |
| 711 | if( g.argc >= 4 ) zBrNameGlob = g.argv[3]; |
| 712 | } |
| 713 | |
| 714 | if( g.localOpen ){ |
| 715 | vid = db_lget_int("checkout", 0); |
| 716 | zCurrent = db_text(0, "SELECT value FROM tagxref" |
| 717 | " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); |
| 718 | } |
| 719 | branch_prepare_list_query(&q, brFlags, zBrNameGlob, nLimit); |
| 720 | while( db_step(&q)==SQLITE_ROW ){ |
| 721 | const char *zBr = db_column_text(&q, 0); |
| 722 | int isPriv = zCurrent!=0 && db_column_int(&q, 1)==1; |
| 723 | int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0; |
| 724 | fossil_print("%s%s%s\n", |
| @@ -703,11 +748,11 @@ | |
| 748 | usage("branch unhide branch-name(s)..."); |
| 749 | } |
| 750 | branch_cmd_hide(3,0); |
| 751 | }else{ |
| 752 | fossil_fatal("branch subcommand should be one of: " |
| 753 | "close current hide info list ls lsh new reopen unhide"); |
| 754 | } |
| 755 | } |
| 756 | |
| 757 | /* |
| 758 | ** This is the new-style branch-list page that shows the branch names |
| @@ -862,11 +907,11 @@ | |
| 907 | @ reopened).</li> |
| 908 | @ </ol> |
| 909 | style_sidebox_end(); |
| 910 | #endif |
| 911 | |
| 912 | branch_prepare_list_query(&q, brFlags, 0, 0); |
| 913 | cnt = 0; |
| 914 | while( db_step(&q)==SQLITE_ROW ){ |
| 915 | const char *zBr = db_column_text(&q, 0); |
| 916 | if( cnt==0 ){ |
| 917 | if( colorTest ){ |
| 918 |
+1
-1
| --- src/json_branch.c | ||
| +++ src/json_branch.c | ||
| @@ -128,11 +128,11 @@ | ||
| 128 | 128 | cson_object_set(pay,"current",json_new_string(zCurrent)); |
| 129 | 129 | } |
| 130 | 130 | } |
| 131 | 131 | |
| 132 | 132 | |
| 133 | - branch_prepare_list_query(&q, branchListFlags, 0); | |
| 133 | + branch_prepare_list_query(&q, branchListFlags, 0, 0); | |
| 134 | 134 | cson_object_set(pay,"branches",listV); |
| 135 | 135 | while((SQLITE_ROW==db_step(&q))){ |
| 136 | 136 | cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); |
| 137 | 137 | if(v){ |
| 138 | 138 | cson_array_append(list,v); |
| 139 | 139 |
| --- src/json_branch.c | |
| +++ src/json_branch.c | |
| @@ -128,11 +128,11 @@ | |
| 128 | cson_object_set(pay,"current",json_new_string(zCurrent)); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | |
| 133 | branch_prepare_list_query(&q, branchListFlags, 0); |
| 134 | cson_object_set(pay,"branches",listV); |
| 135 | while((SQLITE_ROW==db_step(&q))){ |
| 136 | cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); |
| 137 | if(v){ |
| 138 | cson_array_append(list,v); |
| 139 |
| --- src/json_branch.c | |
| +++ src/json_branch.c | |
| @@ -128,11 +128,11 @@ | |
| 128 | cson_object_set(pay,"current",json_new_string(zCurrent)); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | |
| 133 | branch_prepare_list_query(&q, branchListFlags, 0, 0); |
| 134 | cson_object_set(pay,"branches",listV); |
| 135 | while((SQLITE_ROW==db_step(&q))){ |
| 136 | cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); |
| 137 | if(v){ |
| 138 | cson_array_append(list,v); |
| 139 |
+25
-16
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -1471,21 +1471,24 @@ | ||
| 1471 | 1471 | /* |
| 1472 | 1472 | ** If running as root, chroot to the directory containing the |
| 1473 | 1473 | ** repository zRepo and then drop root privileges. Return the |
| 1474 | 1474 | ** new repository name. |
| 1475 | 1475 | ** |
| 1476 | -** zRepo might be a directory itself. In that case chroot into | |
| 1477 | -** the directory zRepo. | |
| 1476 | +** zRepo can be a directory. If so and if the repo name was saved | |
| 1477 | +** to g.zRepositoryName before we were called, we canonicalize the | |
| 1478 | +** two paths and check that one is the prefix of the other, else you | |
| 1479 | +** won't be able to open the repo inside the jail. If it all works | |
| 1480 | +** out, we return the "jailed" version of the repo name. | |
| 1478 | 1481 | ** |
| 1479 | 1482 | ** Assume the user-id and group-id of the repository, or if zRepo |
| 1480 | 1483 | ** is a directory, of that directory. |
| 1481 | 1484 | ** |
| 1482 | 1485 | ** The noJail flag means that the chroot jail is not entered. But |
| 1483 | 1486 | ** privileges are still lowered to that of the user-id and group-id |
| 1484 | 1487 | ** of the repository file. |
| 1485 | 1488 | */ |
| 1486 | -char *enter_chroot_jail(char *zRepo, int noJail){ | |
| 1489 | +static char *enter_chroot_jail(const char *zRepo, int noJail){ | |
| 1487 | 1490 | #if !defined(_WIN32) |
| 1488 | 1491 | if( getuid()==0 ){ |
| 1489 | 1492 | int i; |
| 1490 | 1493 | struct stat sStat; |
| 1491 | 1494 | Blob dir; |
| @@ -1496,15 +1499,27 @@ | ||
| 1496 | 1499 | |
| 1497 | 1500 | file_canonical_name(zRepo, &dir, 0); |
| 1498 | 1501 | zDir = blob_str(&dir); |
| 1499 | 1502 | if( !noJail ){ |
| 1500 | 1503 | if( file_isdir(zDir, ExtFILE)==1 ){ |
| 1504 | + if( g.zRepositoryName ){ | |
| 1505 | + size_t n = strlen(zDir); | |
| 1506 | + Blob repo; | |
| 1507 | + file_canonical_name(g.zRepositoryName, &repo, 0); | |
| 1508 | + zRepo = blob_str(&repo); | |
| 1509 | + if( strncmp(zRepo, zDir, n)!=0 ){ | |
| 1510 | + fossil_fatal("repo %s not under chroot dir %s", zRepo, zDir); | |
| 1511 | + } | |
| 1512 | + zRepo += n; | |
| 1513 | + if( *zRepo == '\0' ) zRepo = "/"; | |
| 1514 | + }else { | |
| 1515 | + zRepo = "/"; | |
| 1516 | + g.fJail = 1; | |
| 1517 | + } | |
| 1501 | 1518 | if( file_chdir(zDir, 1) ){ |
| 1502 | 1519 | fossil_panic("unable to chroot into %s", zDir); |
| 1503 | 1520 | } |
| 1504 | - g.fJail = 1; | |
| 1505 | - zRepo = "/"; | |
| 1506 | 1521 | }else{ |
| 1507 | 1522 | for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} |
| 1508 | 1523 | if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); |
| 1509 | 1524 | if( i>0 ){ |
| 1510 | 1525 | zDir[i] = 0; |
| @@ -1527,11 +1542,11 @@ | ||
| 1527 | 1542 | if( g.db==0 && file_isfile(zRepo, ExtFILE) ){ |
| 1528 | 1543 | db_open_repository(zRepo); |
| 1529 | 1544 | } |
| 1530 | 1545 | } |
| 1531 | 1546 | #endif |
| 1532 | - return zRepo; | |
| 1547 | + return (char*)zRepo; /* no longer const: always reassigned from blob_str() */ | |
| 1533 | 1548 | } |
| 1534 | 1549 | |
| 1535 | 1550 | /* |
| 1536 | 1551 | ** Called whenever a crash is encountered while processing a webpage. |
| 1537 | 1552 | */ |
| @@ -2814,15 +2829,12 @@ | ||
| 2814 | 2829 | zIpAddr = cgi_ssh_remote_addr(0); |
| 2815 | 2830 | if( zIpAddr && zIpAddr[0] ){ |
| 2816 | 2831 | g.fSshClient |= CGI_SSH_CLIENT; |
| 2817 | 2832 | } |
| 2818 | 2833 | } |
| 2819 | - if( zChRoot ){ | |
| 2820 | - enter_chroot_jail((char*)zChRoot, noJail); | |
| 2821 | - }else{ | |
| 2822 | - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); | |
| 2823 | - } | |
| 2834 | + g.zRepositoryName = enter_chroot_jail( | |
| 2835 | + zChRoot ? zChRoot : g.zRepositoryName, noJail); | |
| 2824 | 2836 | if( useSCGI ){ |
| 2825 | 2837 | cgi_handle_scgi_request(); |
| 2826 | 2838 | }else if( g.fSshClient & CGI_SSH_CLIENT ){ |
| 2827 | 2839 | ssh_request_loop(zIpAddr, glob_create(zFileGlob)); |
| 2828 | 2840 | }else{ |
| @@ -3319,15 +3331,12 @@ | ||
| 3319 | 3331 | g.cgiOutput = 1; |
| 3320 | 3332 | find_server_repository(2, 0); |
| 3321 | 3333 | if( fossil_strcmp(g.zRepositoryName,"/")==0 ){ |
| 3322 | 3334 | allowRepoList = 1; |
| 3323 | 3335 | }else{ |
| 3324 | - if( zChRoot ){ | |
| 3325 | - enter_chroot_jail((char*)zChRoot, noJail); | |
| 3326 | - }else{ | |
| 3327 | - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); | |
| 3328 | - } | |
| 3336 | + g.zRepositoryName = enter_chroot_jail( | |
| 3337 | + zChRoot ? zChRoot : g.zRepositoryName, noJail); | |
| 3329 | 3338 | } |
| 3330 | 3339 | if( flags & HTTP_SERVER_SCGI ){ |
| 3331 | 3340 | cgi_handle_scgi_request(); |
| 3332 | 3341 | }else if( g.httpUseSSL ){ |
| 3333 | 3342 | #if FOSSIL_ENABLE_SSL |
| 3334 | 3343 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -1471,21 +1471,24 @@ | |
| 1471 | /* |
| 1472 | ** If running as root, chroot to the directory containing the |
| 1473 | ** repository zRepo and then drop root privileges. Return the |
| 1474 | ** new repository name. |
| 1475 | ** |
| 1476 | ** zRepo might be a directory itself. In that case chroot into |
| 1477 | ** the directory zRepo. |
| 1478 | ** |
| 1479 | ** Assume the user-id and group-id of the repository, or if zRepo |
| 1480 | ** is a directory, of that directory. |
| 1481 | ** |
| 1482 | ** The noJail flag means that the chroot jail is not entered. But |
| 1483 | ** privileges are still lowered to that of the user-id and group-id |
| 1484 | ** of the repository file. |
| 1485 | */ |
| 1486 | char *enter_chroot_jail(char *zRepo, int noJail){ |
| 1487 | #if !defined(_WIN32) |
| 1488 | if( getuid()==0 ){ |
| 1489 | int i; |
| 1490 | struct stat sStat; |
| 1491 | Blob dir; |
| @@ -1496,15 +1499,27 @@ | |
| 1496 | |
| 1497 | file_canonical_name(zRepo, &dir, 0); |
| 1498 | zDir = blob_str(&dir); |
| 1499 | if( !noJail ){ |
| 1500 | if( file_isdir(zDir, ExtFILE)==1 ){ |
| 1501 | if( file_chdir(zDir, 1) ){ |
| 1502 | fossil_panic("unable to chroot into %s", zDir); |
| 1503 | } |
| 1504 | g.fJail = 1; |
| 1505 | zRepo = "/"; |
| 1506 | }else{ |
| 1507 | for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} |
| 1508 | if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); |
| 1509 | if( i>0 ){ |
| 1510 | zDir[i] = 0; |
| @@ -1527,11 +1542,11 @@ | |
| 1527 | if( g.db==0 && file_isfile(zRepo, ExtFILE) ){ |
| 1528 | db_open_repository(zRepo); |
| 1529 | } |
| 1530 | } |
| 1531 | #endif |
| 1532 | return zRepo; |
| 1533 | } |
| 1534 | |
| 1535 | /* |
| 1536 | ** Called whenever a crash is encountered while processing a webpage. |
| 1537 | */ |
| @@ -2814,15 +2829,12 @@ | |
| 2814 | zIpAddr = cgi_ssh_remote_addr(0); |
| 2815 | if( zIpAddr && zIpAddr[0] ){ |
| 2816 | g.fSshClient |= CGI_SSH_CLIENT; |
| 2817 | } |
| 2818 | } |
| 2819 | if( zChRoot ){ |
| 2820 | enter_chroot_jail((char*)zChRoot, noJail); |
| 2821 | }else{ |
| 2822 | g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); |
| 2823 | } |
| 2824 | if( useSCGI ){ |
| 2825 | cgi_handle_scgi_request(); |
| 2826 | }else if( g.fSshClient & CGI_SSH_CLIENT ){ |
| 2827 | ssh_request_loop(zIpAddr, glob_create(zFileGlob)); |
| 2828 | }else{ |
| @@ -3319,15 +3331,12 @@ | |
| 3319 | g.cgiOutput = 1; |
| 3320 | find_server_repository(2, 0); |
| 3321 | if( fossil_strcmp(g.zRepositoryName,"/")==0 ){ |
| 3322 | allowRepoList = 1; |
| 3323 | }else{ |
| 3324 | if( zChRoot ){ |
| 3325 | enter_chroot_jail((char*)zChRoot, noJail); |
| 3326 | }else{ |
| 3327 | g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); |
| 3328 | } |
| 3329 | } |
| 3330 | if( flags & HTTP_SERVER_SCGI ){ |
| 3331 | cgi_handle_scgi_request(); |
| 3332 | }else if( g.httpUseSSL ){ |
| 3333 | #if FOSSIL_ENABLE_SSL |
| 3334 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -1471,21 +1471,24 @@ | |
| 1471 | /* |
| 1472 | ** If running as root, chroot to the directory containing the |
| 1473 | ** repository zRepo and then drop root privileges. Return the |
| 1474 | ** new repository name. |
| 1475 | ** |
| 1476 | ** zRepo can be a directory. If so and if the repo name was saved |
| 1477 | ** to g.zRepositoryName before we were called, we canonicalize the |
| 1478 | ** two paths and check that one is the prefix of the other, else you |
| 1479 | ** won't be able to open the repo inside the jail. If it all works |
| 1480 | ** out, we return the "jailed" version of the repo name. |
| 1481 | ** |
| 1482 | ** Assume the user-id and group-id of the repository, or if zRepo |
| 1483 | ** is a directory, of that directory. |
| 1484 | ** |
| 1485 | ** The noJail flag means that the chroot jail is not entered. But |
| 1486 | ** privileges are still lowered to that of the user-id and group-id |
| 1487 | ** of the repository file. |
| 1488 | */ |
| 1489 | static char *enter_chroot_jail(const char *zRepo, int noJail){ |
| 1490 | #if !defined(_WIN32) |
| 1491 | if( getuid()==0 ){ |
| 1492 | int i; |
| 1493 | struct stat sStat; |
| 1494 | Blob dir; |
| @@ -1496,15 +1499,27 @@ | |
| 1499 | |
| 1500 | file_canonical_name(zRepo, &dir, 0); |
| 1501 | zDir = blob_str(&dir); |
| 1502 | if( !noJail ){ |
| 1503 | if( file_isdir(zDir, ExtFILE)==1 ){ |
| 1504 | if( g.zRepositoryName ){ |
| 1505 | size_t n = strlen(zDir); |
| 1506 | Blob repo; |
| 1507 | file_canonical_name(g.zRepositoryName, &repo, 0); |
| 1508 | zRepo = blob_str(&repo); |
| 1509 | if( strncmp(zRepo, zDir, n)!=0 ){ |
| 1510 | fossil_fatal("repo %s not under chroot dir %s", zRepo, zDir); |
| 1511 | } |
| 1512 | zRepo += n; |
| 1513 | if( *zRepo == '\0' ) zRepo = "/"; |
| 1514 | }else { |
| 1515 | zRepo = "/"; |
| 1516 | g.fJail = 1; |
| 1517 | } |
| 1518 | if( file_chdir(zDir, 1) ){ |
| 1519 | fossil_panic("unable to chroot into %s", zDir); |
| 1520 | } |
| 1521 | }else{ |
| 1522 | for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} |
| 1523 | if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); |
| 1524 | if( i>0 ){ |
| 1525 | zDir[i] = 0; |
| @@ -1527,11 +1542,11 @@ | |
| 1542 | if( g.db==0 && file_isfile(zRepo, ExtFILE) ){ |
| 1543 | db_open_repository(zRepo); |
| 1544 | } |
| 1545 | } |
| 1546 | #endif |
| 1547 | return (char*)zRepo; /* no longer const: always reassigned from blob_str() */ |
| 1548 | } |
| 1549 | |
| 1550 | /* |
| 1551 | ** Called whenever a crash is encountered while processing a webpage. |
| 1552 | */ |
| @@ -2814,15 +2829,12 @@ | |
| 2829 | zIpAddr = cgi_ssh_remote_addr(0); |
| 2830 | if( zIpAddr && zIpAddr[0] ){ |
| 2831 | g.fSshClient |= CGI_SSH_CLIENT; |
| 2832 | } |
| 2833 | } |
| 2834 | g.zRepositoryName = enter_chroot_jail( |
| 2835 | zChRoot ? zChRoot : g.zRepositoryName, noJail); |
| 2836 | if( useSCGI ){ |
| 2837 | cgi_handle_scgi_request(); |
| 2838 | }else if( g.fSshClient & CGI_SSH_CLIENT ){ |
| 2839 | ssh_request_loop(zIpAddr, glob_create(zFileGlob)); |
| 2840 | }else{ |
| @@ -3319,15 +3331,12 @@ | |
| 3331 | g.cgiOutput = 1; |
| 3332 | find_server_repository(2, 0); |
| 3333 | if( fossil_strcmp(g.zRepositoryName,"/")==0 ){ |
| 3334 | allowRepoList = 1; |
| 3335 | }else{ |
| 3336 | g.zRepositoryName = enter_chroot_jail( |
| 3337 | zChRoot ? zChRoot : g.zRepositoryName, noJail); |
| 3338 | } |
| 3339 | if( flags & HTTP_SERVER_SCGI ){ |
| 3340 | cgi_handle_scgi_request(); |
| 3341 | }else if( g.httpUseSSL ){ |
| 3342 | #if FOSSIL_ENABLE_SSL |
| 3343 |
+1
-1
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -1102,11 +1102,11 @@ | ||
| 1102 | 1102 | @ engines as well as a short RSS description. |
| 1103 | 1103 | @ (Property: "project-description")</p> |
| 1104 | 1104 | @ <hr /> |
| 1105 | 1105 | entry_attribute("Canonical Server URL", 40, "email-url", |
| 1106 | 1106 | "eurl", "", 0); |
| 1107 | - @ <p>This is the URL used access this repository as a server. | |
| 1107 | + @ <p>This is the URL used to access this repository as a server. | |
| 1108 | 1108 | @ Other repositories use this URL to clone or sync against this repository. |
| 1109 | 1109 | @ This is also the basename for hyperlinks included in email alert text. |
| 1110 | 1110 | @ Omit the trailing "/". |
| 1111 | 1111 | @ If this repo will not be set up as a persistent server and will not |
| 1112 | 1112 | @ be sending email alerts, then leave this entry blank. |
| 1113 | 1113 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -1102,11 +1102,11 @@ | |
| 1102 | @ engines as well as a short RSS description. |
| 1103 | @ (Property: "project-description")</p> |
| 1104 | @ <hr /> |
| 1105 | entry_attribute("Canonical Server URL", 40, "email-url", |
| 1106 | "eurl", "", 0); |
| 1107 | @ <p>This is the URL used access this repository as a server. |
| 1108 | @ Other repositories use this URL to clone or sync against this repository. |
| 1109 | @ This is also the basename for hyperlinks included in email alert text. |
| 1110 | @ Omit the trailing "/". |
| 1111 | @ If this repo will not be set up as a persistent server and will not |
| 1112 | @ be sending email alerts, then leave this entry blank. |
| 1113 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -1102,11 +1102,11 @@ | |
| 1102 | @ engines as well as a short RSS description. |
| 1103 | @ (Property: "project-description")</p> |
| 1104 | @ <hr /> |
| 1105 | entry_attribute("Canonical Server URL", 40, "email-url", |
| 1106 | "eurl", "", 0); |
| 1107 | @ <p>This is the URL used to access this repository as a server. |
| 1108 | @ Other repositories use this URL to clone or sync against this repository. |
| 1109 | @ This is also the basename for hyperlinks included in email alert text. |
| 1110 | @ Omit the trailing "/". |
| 1111 | @ If this repo will not be set up as a persistent server and will not |
| 1112 | @ be sending email alerts, then leave this entry blank. |
| 1113 |
+1
-1
| --- src/stat.c | ||
| +++ src/stat.c | ||
| @@ -231,11 +231,11 @@ | ||
| 231 | 231 | @ <tr><th>Number Of Wiki Pages:</th><td> |
| 232 | 232 | n = db_int(0, "SELECT count(*) FROM tag /*scan*/" |
| 233 | 233 | " WHERE +tagname GLOB 'wiki-*'"); |
| 234 | 234 | @ %,d(n) |
| 235 | 235 | @ </td></tr> |
| 236 | - if( db_table_exists("repository","chat") ){ | |
| 236 | + if( g.perm.Chat && db_table_exists("repository","chat") ){ | |
| 237 | 237 | sqlite3_int64 sz = 0; |
| 238 | 238 | char zSz[100]; |
| 239 | 239 | n = db_int(0, "SELECT max(msgid) FROM chat"); |
| 240 | 240 | m = db_int(0, "SELECT count(*) FROM chat WHERE mdel IS NOT TRUE"); |
| 241 | 241 | sz = db_int64(0, "SELECT sum(coalesce(length(xmsg),0)+" |
| 242 | 242 |
| --- src/stat.c | |
| +++ src/stat.c | |
| @@ -231,11 +231,11 @@ | |
| 231 | @ <tr><th>Number Of Wiki Pages:</th><td> |
| 232 | n = db_int(0, "SELECT count(*) FROM tag /*scan*/" |
| 233 | " WHERE +tagname GLOB 'wiki-*'"); |
| 234 | @ %,d(n) |
| 235 | @ </td></tr> |
| 236 | if( db_table_exists("repository","chat") ){ |
| 237 | sqlite3_int64 sz = 0; |
| 238 | char zSz[100]; |
| 239 | n = db_int(0, "SELECT max(msgid) FROM chat"); |
| 240 | m = db_int(0, "SELECT count(*) FROM chat WHERE mdel IS NOT TRUE"); |
| 241 | sz = db_int64(0, "SELECT sum(coalesce(length(xmsg),0)+" |
| 242 |
| --- src/stat.c | |
| +++ src/stat.c | |
| @@ -231,11 +231,11 @@ | |
| 231 | @ <tr><th>Number Of Wiki Pages:</th><td> |
| 232 | n = db_int(0, "SELECT count(*) FROM tag /*scan*/" |
| 233 | " WHERE +tagname GLOB 'wiki-*'"); |
| 234 | @ %,d(n) |
| 235 | @ </td></tr> |
| 236 | if( g.perm.Chat && db_table_exists("repository","chat") ){ |
| 237 | sqlite3_int64 sz = 0; |
| 238 | char zSz[100]; |
| 239 | n = db_int(0, "SELECT max(msgid) FROM chat"); |
| 240 | m = db_int(0, "SELECT count(*) FROM chat WHERE mdel IS NOT TRUE"); |
| 241 | sz = db_int64(0, "SELECT sum(coalesce(length(xmsg),0)+" |
| 242 |
+130
-76
| --- www/build.wiki | ||
| +++ www/build.wiki | ||
| @@ -112,11 +112,12 @@ | ||
| 112 | 112 | <p>For more advanced use cases, see the [./ssl.wiki#openssl-bin|OpenSSL |
| 113 | 113 | discussion in the "TLS and Fossil" document].</p> |
| 114 | 114 | |
| 115 | 115 | <li><p> |
| 116 | 116 | To build a statically linked binary (suitable for use inside a chroot |
| 117 | -jail) add the <b>--static</b> option. | |
| 117 | +jail) add the <b>--static</b> option. (See the [#docker | Docker section | |
| 118 | +below].) | |
| 118 | 119 | |
| 119 | 120 | <li><p> |
| 120 | 121 | To enable the native [./th1.md#tclEval | Tcl integration feature] feature, |
| 121 | 122 | add the <b>--with-tcl=1</b> and <b>--with-tcl-private-stubs=1</b> options. |
| 122 | 123 | |
| @@ -248,85 +249,138 @@ | ||
| 248 | 249 | TCC += -Dsocketlen_t=int |
| 249 | 250 | TCC += -DSQLITE_MAX_MMAP_SIZE=0 |
| 250 | 251 | </pre></blockquote> |
| 251 | 252 | </ul> |
| 252 | 253 | |
| 253 | -<h2>5.0 Building a Static Binary on Linux using Docker</h2> | |
| 254 | - | |
| 255 | -Building a static binary on Linux is not as straightforward as it | |
| 256 | -could be because the GNU C library requires that certain components be | |
| 257 | -dynamically loadable. That can be worked around by building against a | |
| 258 | -different C library, which is simplest to do by way of a container | |
| 259 | -environment like [https://www.docker.com/ | Docker]. | |
| 260 | - | |
| 261 | -The following instructions for building fossil using Docker | |
| 262 | -were adapted from [https://fossil-scm.org/forum/forumpost/5dd2d61e5f | forumpost/5dd2d61e5f]. | |
| 263 | -These instructions assume that docker is installed and that the user running | |
| 264 | -these instructions has permission to do so (i.e., they are <tt>root</tt> or | |
| 265 | -are a member of the <tt>docker</tt> group). | |
| 266 | - | |
| 267 | -First, create a file named <tt>Dockerfile</tt> with the following contents: | |
| 268 | - | |
| 269 | -<pre><code> | |
| 270 | -FROM alpine:edge | |
| 271 | -RUN apk update \ | |
| 272 | - && apk upgrade \ | |
| 273 | - && apk add --no-cache \ | |
| 274 | - curl gcc make tcl \ | |
| 275 | - musl-dev \ | |
| 276 | - openssl-dev zlib-dev \ | |
| 277 | - openssl-libs-static zlib-static \ | |
| 278 | - && curl \ | |
| 279 | - "https://fossil-scm.org/home/tarball/fossil-src.tar.gz?name=fossil-src&uuid=trunk" \ | |
| 280 | - -o fossil-src.tar.gz \ | |
| 281 | - && tar xf fossil-src.tar.gz \ | |
| 282 | - && cd fossil-src \ | |
| 283 | - && ./configure \ | |
| 284 | - --static \ | |
| 285 | - --disable-fusefs \ | |
| 286 | - --with-th1-docs \ | |
| 287 | - --with-th1-hooks \ | |
| 288 | - && make | |
| 289 | -</code></pre> | |
| 290 | - | |
| 291 | -Be sure to modify the <tt>configure</tt> flags, if desired. e.g., add <tt>--json</tt> | |
| 292 | -for JSON support. | |
| 293 | - | |
| 294 | -From the directory containing that file, build it with docker: | |
| 295 | - | |
| 296 | -<pre><code># docker build -t fossil_static .</code></pre> | |
| 297 | - | |
| 298 | -If you get permissions errors when running that as a non-root user, | |
| 299 | -be sure to add the user to the <tt>docker</tt> group before trying | |
| 300 | -again. | |
| 301 | - | |
| 302 | -That creates a docker image and builds a static fossil binary inside | |
| 303 | -it. That step will take several minutes or more, depending on the | |
| 304 | -speed of the build environment. | |
| 305 | - | |
| 306 | -Next, create a docker container to host the image we just created: | |
| 307 | - | |
| 308 | -<pre><code># docker create --name fossil fossil_static</code></pre> | |
| 309 | - | |
| 310 | -Then copy the fossil binary from that container: | |
| 311 | - | |
| 312 | -<pre><code># docker cp fossil:/fossil-src/fossil fossil</code></pre> | |
| 313 | - | |
| 314 | -The resulting binary will be <em>huge</em> because it is built with | |
| 315 | -debug info. To strip that information, reducing the size greatly: | |
| 316 | - | |
| 317 | -<pre><code># strip fossil</code></pre> | |
| 318 | - | |
| 319 | -To delete the Docker container and image (if desired), run: | |
| 320 | - | |
| 321 | -<pre><code># docker container rm fossil | |
| 322 | -# docker image ls | |
| 323 | -</code></pre> | |
| 324 | - | |
| 325 | -Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then: | |
| 326 | - | |
| 327 | -<pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre> | |
| 254 | + | |
| 255 | +<h2 id="docker">5.0 Building a Docker Container</h2> | |
| 256 | + | |
| 257 | +Fossil ships a <tt>Dockerfile</tt> at the top of its source tree which | |
| 258 | +you can build like so: | |
| 259 | + | |
| 260 | +<pre><code> $ docker build -t fossil --no-cache .</code></pre> | |
| 261 | + | |
| 262 | +If the image built successfully, you can create a container from it and | |
| 263 | +test that it runs: | |
| 264 | + | |
| 265 | +<pre><code> $ docker run --name fossil -p 9999:8080/tcp fossil</code></pre> | |
| 266 | + | |
| 267 | +This shows us remapping the internal TCP listening port as 9999 on the | |
| 268 | +host. As a rule, there's little point to using the "<tt>fossil server | |
| 269 | +--port</tt>" feature inside a Docker container. Let it default to 8080 | |
| 270 | +internally, then remap it to wherever you want it on the host instead. | |
| 271 | + | |
| 272 | +Our stock <tt>Dockerfile</tt> configures Fossil with the default feature | |
| 273 | +set, so you may wish to modify the <tt>Dockerfile</tt> to add | |
| 274 | +configuration options, add APK packages to support those options, and so | |
| 275 | +forth. | |
| 276 | + | |
| 277 | +It builds tip-of-trunk. To get a release version instead, append | |
| 278 | +"<tt>?r=release</tt>" to the URL in the <tt>Dockerfile</tt>, then | |
| 279 | +(re)build it. | |
| 280 | + | |
| 281 | + | |
| 282 | +<h3>5.1 Running It in Production</h3> | |
| 283 | + | |
| 284 | +If you want the container to serve an existing repository, there are at | |
| 285 | +least two right ways to do it. | |
| 286 | + | |
| 287 | +The wrong way is to use the <tt>Dockerfile COPY</tt> command to bake it | |
| 288 | +into the container at build time. It will become one of the container's | |
| 289 | +base layers, so that each time you restart the container, it gets reset | |
| 290 | +to its prior state. This almost certainly is not what you want. | |
| 291 | + | |
| 292 | +The simplest correct method is to stop the container if it was running, | |
| 293 | +then say: | |
| 294 | + | |
| 295 | +<pre><code> $ docker cp /path/to/repo.fossil fossil:/jail/museum/repo.fossil | |
| 296 | + $ docker start fossil | |
| 297 | + $ docker exec fossil chown 0 /jail/museum/repo.fossil</code></pre> | |
| 298 | + | |
| 299 | +That copies the local Fossil repo into the container where the server | |
| 300 | +expects to find it, so that the "start" command causes it to serve from | |
| 301 | +that copied-in file instead. Since it lives atop the base layers, it | |
| 302 | +persists as part of the container proper, surviving restarts. | |
| 303 | + | |
| 304 | +(The same is true of the default mode of operation: the <tt>fossil | |
| 305 | +server --create</tt> flag initializes a fresh Fossil repo atop the base | |
| 306 | +layer.) | |
| 307 | + | |
| 308 | +If you skip the "chown" command and put "http://localhost:9999/" into | |
| 309 | +your browser, expecting to see the copied-in repo's home page, you will | |
| 310 | +get an opaque "Not Found" error instead. This is because the user and | |
| 311 | +group ID of the file will be that of your local user on the container's | |
| 312 | +host machine, which won't map to anything in the container's | |
| 313 | +<tt>/etc/passwd</tt> and <tt>/etc/group</tt> files, effectively | |
| 314 | +preventing the server from reading the copied-in repository file. You | |
| 315 | +don't have to restart the server after fixing this: simply reload the | |
| 316 | +browser, and Fossil will try again. | |
| 317 | + | |
| 318 | +This simple method has a problem: Docker containers are designed to be | |
| 319 | +killed off at the slightest cause, rebuilt, and redeployed. If you do | |
| 320 | +that with the repo inside the container, it gets destroyed, too. The | |
| 321 | +solution is to replace the "run" command above with the following: | |
| 322 | + | |
| 323 | +<pre><code> $ docker create \ | |
| 324 | + --name fossil -p 9999:8080/tcp \ | |
| 325 | + -v museum:/jail/museum fossil | |
| 326 | +</code></pre> | |
| 327 | + | |
| 328 | +Now when you "docker cp" the local repo into the container, it lands on | |
| 329 | +a separate [https://docs.docker.com/storage/volumes/ | Docker volume] | |
| 330 | +mounted inside it, which has an independent lifetime. When you need to | |
| 331 | +rebuild the container — such as to upgrade to a newer version of Fossil | |
| 332 | +— the volume remains behind and gets remapped into the new contanier | |
| 333 | +when you recreate it by giving the above command again. | |
| 334 | + | |
| 335 | + | |
| 336 | +<h3>5.2 Why Chroot?</h3> | |
| 337 | + | |
| 338 | +A potentially surprising feature of this container is that it runs | |
| 339 | +Fossil as root, which causes [./chroot.md | Fossil's chroot jail | |
| 340 | +feature] to kick in. Since a Docker container is a type of über-jail | |
| 341 | +already, you may be wondering why we don't either: | |
| 342 | + | |
| 343 | + # run <tt>fossil server --nojail</tt> to skip the internal chroot; or | |
| 344 | + # create a non-root user and force Docker to use that instead | |
| 345 | + | |
| 346 | +The reason is, although this container is quite stripped-down by today's | |
| 347 | +standards, it's based on the [https://www.busybox.net/BusyBox.html | | |
| 348 | +surprisingly powerful Busybox project]. (This author made a living for | |
| 349 | +years in the early 1990s using Unix systems that were less powerful than | |
| 350 | +this container.) If someone ever figured out how to make a Fossil binary | |
| 351 | +execute arbitrary commands on the host or to open up a remote shell, | |
| 352 | +they'd likely be able to island-hop from there into the rest of your | |
| 353 | +network. We need this cute double-jail dance to keep the Fossil instance | |
| 354 | +from accessing the Busybox features. | |
| 355 | + | |
| 356 | +We deem this risk low since a) it's never happened, that we know of; | |
| 357 | +and b) we've turned off all of the risky features like TH1 docs. | |
| 358 | +Nevertheless, we believe in defense-in-depth. | |
| 359 | + | |
| 360 | + | |
| 361 | +<h3>5.3 Extracting a Static Binary</h3> | |
| 362 | + | |
| 363 | +Our 2-stage build process uses Alpine Linux only as a build host. Once | |
| 364 | +we've got everything reduced to the two key static binaries — Fossil and | |
| 365 | +Busybox — we throw all the rest of it away. | |
| 366 | + | |
| 367 | +A secondary benefit falls out of this process for free: it's arguably | |
| 368 | +the easiest way to build a purely static Fossil binary for Linux. Most | |
| 369 | +modern Linux distros make this surprisingly difficult, but Alpine's | |
| 370 | +back-to-basics nature makes static builds work the way they used to, | |
| 371 | +back in the day. If that's what you're after, you can skip the "run" | |
| 372 | +command above and create a temporary container from the image, then | |
| 373 | +extract the executable from it instead: | |
| 374 | + | |
| 375 | +<pre><code> $ docker create --name fossil-static-tmp fossil | |
| 376 | + $ docker cp fossil-static-tmp:/jail/bin/fossil . | |
| 377 | + $ docker container rm fossil-static-tmp | |
| 378 | +</code></pre> | |
| 379 | + | |
| 380 | +The resulting binary is the single largest file inside that container, | |
| 381 | +at about 6 MiB. (It's built stripped.) | |
| 328 | 382 | |
| 329 | 383 | |
| 330 | 384 | <h2>6.0 Building on/for Android</h2> |
| 331 | 385 | |
| 332 | 386 | <h3>6.1 Cross-compiling from Linux</h3> |
| 333 | 387 |
| --- www/build.wiki | |
| +++ www/build.wiki | |
| @@ -112,11 +112,12 @@ | |
| 112 | <p>For more advanced use cases, see the [./ssl.wiki#openssl-bin|OpenSSL |
| 113 | discussion in the "TLS and Fossil" document].</p> |
| 114 | |
| 115 | <li><p> |
| 116 | To build a statically linked binary (suitable for use inside a chroot |
| 117 | jail) add the <b>--static</b> option. |
| 118 | |
| 119 | <li><p> |
| 120 | To enable the native [./th1.md#tclEval | Tcl integration feature] feature, |
| 121 | add the <b>--with-tcl=1</b> and <b>--with-tcl-private-stubs=1</b> options. |
| 122 | |
| @@ -248,85 +249,138 @@ | |
| 248 | TCC += -Dsocketlen_t=int |
| 249 | TCC += -DSQLITE_MAX_MMAP_SIZE=0 |
| 250 | </pre></blockquote> |
| 251 | </ul> |
| 252 | |
| 253 | <h2>5.0 Building a Static Binary on Linux using Docker</h2> |
| 254 | |
| 255 | Building a static binary on Linux is not as straightforward as it |
| 256 | could be because the GNU C library requires that certain components be |
| 257 | dynamically loadable. That can be worked around by building against a |
| 258 | different C library, which is simplest to do by way of a container |
| 259 | environment like [https://www.docker.com/ | Docker]. |
| 260 | |
| 261 | The following instructions for building fossil using Docker |
| 262 | were adapted from [https://fossil-scm.org/forum/forumpost/5dd2d61e5f | forumpost/5dd2d61e5f]. |
| 263 | These instructions assume that docker is installed and that the user running |
| 264 | these instructions has permission to do so (i.e., they are <tt>root</tt> or |
| 265 | are a member of the <tt>docker</tt> group). |
| 266 | |
| 267 | First, create a file named <tt>Dockerfile</tt> with the following contents: |
| 268 | |
| 269 | <pre><code> |
| 270 | FROM alpine:edge |
| 271 | RUN apk update \ |
| 272 | && apk upgrade \ |
| 273 | && apk add --no-cache \ |
| 274 | curl gcc make tcl \ |
| 275 | musl-dev \ |
| 276 | openssl-dev zlib-dev \ |
| 277 | openssl-libs-static zlib-static \ |
| 278 | && curl \ |
| 279 | "https://fossil-scm.org/home/tarball/fossil-src.tar.gz?name=fossil-src&uuid=trunk" \ |
| 280 | -o fossil-src.tar.gz \ |
| 281 | && tar xf fossil-src.tar.gz \ |
| 282 | && cd fossil-src \ |
| 283 | && ./configure \ |
| 284 | --static \ |
| 285 | --disable-fusefs \ |
| 286 | --with-th1-docs \ |
| 287 | --with-th1-hooks \ |
| 288 | && make |
| 289 | </code></pre> |
| 290 | |
| 291 | Be sure to modify the <tt>configure</tt> flags, if desired. e.g., add <tt>--json</tt> |
| 292 | for JSON support. |
| 293 | |
| 294 | From the directory containing that file, build it with docker: |
| 295 | |
| 296 | <pre><code># docker build -t fossil_static .</code></pre> |
| 297 | |
| 298 | If you get permissions errors when running that as a non-root user, |
| 299 | be sure to add the user to the <tt>docker</tt> group before trying |
| 300 | again. |
| 301 | |
| 302 | That creates a docker image and builds a static fossil binary inside |
| 303 | it. That step will take several minutes or more, depending on the |
| 304 | speed of the build environment. |
| 305 | |
| 306 | Next, create a docker container to host the image we just created: |
| 307 | |
| 308 | <pre><code># docker create --name fossil fossil_static</code></pre> |
| 309 | |
| 310 | Then copy the fossil binary from that container: |
| 311 | |
| 312 | <pre><code># docker cp fossil:/fossil-src/fossil fossil</code></pre> |
| 313 | |
| 314 | The resulting binary will be <em>huge</em> because it is built with |
| 315 | debug info. To strip that information, reducing the size greatly: |
| 316 | |
| 317 | <pre><code># strip fossil</code></pre> |
| 318 | |
| 319 | To delete the Docker container and image (if desired), run: |
| 320 | |
| 321 | <pre><code># docker container rm fossil |
| 322 | # docker image ls |
| 323 | </code></pre> |
| 324 | |
| 325 | Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then: |
| 326 | |
| 327 | <pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre> |
| 328 | |
| 329 | |
| 330 | <h2>6.0 Building on/for Android</h2> |
| 331 | |
| 332 | <h3>6.1 Cross-compiling from Linux</h3> |
| 333 |
| --- www/build.wiki | |
| +++ www/build.wiki | |
| @@ -112,11 +112,12 @@ | |
| 112 | <p>For more advanced use cases, see the [./ssl.wiki#openssl-bin|OpenSSL |
| 113 | discussion in the "TLS and Fossil" document].</p> |
| 114 | |
| 115 | <li><p> |
| 116 | To build a statically linked binary (suitable for use inside a chroot |
| 117 | jail) add the <b>--static</b> option. (See the [#docker | Docker section |
| 118 | below].) |
| 119 | |
| 120 | <li><p> |
| 121 | To enable the native [./th1.md#tclEval | Tcl integration feature] feature, |
| 122 | add the <b>--with-tcl=1</b> and <b>--with-tcl-private-stubs=1</b> options. |
| 123 | |
| @@ -248,85 +249,138 @@ | |
| 249 | TCC += -Dsocketlen_t=int |
| 250 | TCC += -DSQLITE_MAX_MMAP_SIZE=0 |
| 251 | </pre></blockquote> |
| 252 | </ul> |
| 253 | |
| 254 | |
| 255 | <h2 id="docker">5.0 Building a Docker Container</h2> |
| 256 | |
| 257 | Fossil ships a <tt>Dockerfile</tt> at the top of its source tree which |
| 258 | you can build like so: |
| 259 | |
| 260 | <pre><code> $ docker build -t fossil --no-cache .</code></pre> |
| 261 | |
| 262 | If the image built successfully, you can create a container from it and |
| 263 | test that it runs: |
| 264 | |
| 265 | <pre><code> $ docker run --name fossil -p 9999:8080/tcp fossil</code></pre> |
| 266 | |
| 267 | This shows us remapping the internal TCP listening port as 9999 on the |
| 268 | host. As a rule, there's little point to using the "<tt>fossil server |
| 269 | --port</tt>" feature inside a Docker container. Let it default to 8080 |
| 270 | internally, then remap it to wherever you want it on the host instead. |
| 271 | |
| 272 | Our stock <tt>Dockerfile</tt> configures Fossil with the default feature |
| 273 | set, so you may wish to modify the <tt>Dockerfile</tt> to add |
| 274 | configuration options, add APK packages to support those options, and so |
| 275 | forth. |
| 276 | |
| 277 | It builds tip-of-trunk. To get a release version instead, append |
| 278 | "<tt>?r=release</tt>" to the URL in the <tt>Dockerfile</tt>, then |
| 279 | (re)build it. |
| 280 | |
| 281 | |
| 282 | <h3>5.1 Running It in Production</h3> |
| 283 | |
| 284 | If you want the container to serve an existing repository, there are at |
| 285 | least two right ways to do it. |
| 286 | |
| 287 | The wrong way is to use the <tt>Dockerfile COPY</tt> command to bake it |
| 288 | into the container at build time. It will become one of the container's |
| 289 | base layers, so that each time you restart the container, it gets reset |
| 290 | to its prior state. This almost certainly is not what you want. |
| 291 | |
| 292 | The simplest correct method is to stop the container if it was running, |
| 293 | then say: |
| 294 | |
| 295 | <pre><code> $ docker cp /path/to/repo.fossil fossil:/jail/museum/repo.fossil |
| 296 | $ docker start fossil |
| 297 | $ docker exec fossil chown 0 /jail/museum/repo.fossil</code></pre> |
| 298 | |
| 299 | That copies the local Fossil repo into the container where the server |
| 300 | expects to find it, so that the "start" command causes it to serve from |
| 301 | that copied-in file instead. Since it lives atop the base layers, it |
| 302 | persists as part of the container proper, surviving restarts. |
| 303 | |
| 304 | (The same is true of the default mode of operation: the <tt>fossil |
| 305 | server --create</tt> flag initializes a fresh Fossil repo atop the base |
| 306 | layer.) |
| 307 | |
| 308 | If you skip the "chown" command and put "http://localhost:9999/" into |
| 309 | your browser, expecting to see the copied-in repo's home page, you will |
| 310 | get an opaque "Not Found" error instead. This is because the user and |
| 311 | group ID of the file will be that of your local user on the container's |
| 312 | host machine, which won't map to anything in the container's |
| 313 | <tt>/etc/passwd</tt> and <tt>/etc/group</tt> files, effectively |
| 314 | preventing the server from reading the copied-in repository file. You |
| 315 | don't have to restart the server after fixing this: simply reload the |
| 316 | browser, and Fossil will try again. |
| 317 | |
| 318 | This simple method has a problem: Docker containers are designed to be |
| 319 | killed off at the slightest cause, rebuilt, and redeployed. If you do |
| 320 | that with the repo inside the container, it gets destroyed, too. The |
| 321 | solution is to replace the "run" command above with the following: |
| 322 | |
| 323 | <pre><code> $ docker create \ |
| 324 | --name fossil -p 9999:8080/tcp \ |
| 325 | -v museum:/jail/museum fossil |
| 326 | </code></pre> |
| 327 | |
| 328 | Now when you "docker cp" the local repo into the container, it lands on |
| 329 | a separate [https://docs.docker.com/storage/volumes/ | Docker volume] |
| 330 | mounted inside it, which has an independent lifetime. When you need to |
| 331 | rebuild the container — such as to upgrade to a newer version of Fossil |
| 332 | — the volume remains behind and gets remapped into the new contanier |
| 333 | when you recreate it by giving the above command again. |
| 334 | |
| 335 | |
| 336 | <h3>5.2 Why Chroot?</h3> |
| 337 | |
| 338 | A potentially surprising feature of this container is that it runs |
| 339 | Fossil as root, which causes [./chroot.md | Fossil's chroot jail |
| 340 | feature] to kick in. Since a Docker container is a type of über-jail |
| 341 | already, you may be wondering why we don't either: |
| 342 | |
| 343 | # run <tt>fossil server --nojail</tt> to skip the internal chroot; or |
| 344 | # create a non-root user and force Docker to use that instead |
| 345 | |
| 346 | The reason is, although this container is quite stripped-down by today's |
| 347 | standards, it's based on the [https://www.busybox.net/BusyBox.html | |
| 348 | surprisingly powerful Busybox project]. (This author made a living for |
| 349 | years in the early 1990s using Unix systems that were less powerful than |
| 350 | this container.) If someone ever figured out how to make a Fossil binary |
| 351 | execute arbitrary commands on the host or to open up a remote shell, |
| 352 | they'd likely be able to island-hop from there into the rest of your |
| 353 | network. We need this cute double-jail dance to keep the Fossil instance |
| 354 | from accessing the Busybox features. |
| 355 | |
| 356 | We deem this risk low since a) it's never happened, that we know of; |
| 357 | and b) we've turned off all of the risky features like TH1 docs. |
| 358 | Nevertheless, we believe in defense-in-depth. |
| 359 | |
| 360 | |
| 361 | <h3>5.3 Extracting a Static Binary</h3> |
| 362 | |
| 363 | Our 2-stage build process uses Alpine Linux only as a build host. Once |
| 364 | we've got everything reduced to the two key static binaries — Fossil and |
| 365 | Busybox — we throw all the rest of it away. |
| 366 | |
| 367 | A secondary benefit falls out of this process for free: it's arguably |
| 368 | the easiest way to build a purely static Fossil binary for Linux. Most |
| 369 | modern Linux distros make this surprisingly difficult, but Alpine's |
| 370 | back-to-basics nature makes static builds work the way they used to, |
| 371 | back in the day. If that's what you're after, you can skip the "run" |
| 372 | command above and create a temporary container from the image, then |
| 373 | extract the executable from it instead: |
| 374 | |
| 375 | <pre><code> $ docker create --name fossil-static-tmp fossil |
| 376 | $ docker cp fossil-static-tmp:/jail/bin/fossil . |
| 377 | $ docker container rm fossil-static-tmp |
| 378 | </code></pre> |
| 379 | |
| 380 | The resulting binary is the single largest file inside that container, |
| 381 | at about 6 MiB. (It's built stripped.) |
| 382 | |
| 383 | |
| 384 | <h2>6.0 Building on/for Android</h2> |
| 385 | |
| 386 | <h3>6.1 Cross-compiling from Linux</h3> |
| 387 |
+54
-36
| --- www/fossil-v-git.wiki | ||
| +++ www/fossil-v-git.wiki | ||
| @@ -113,36 +113,41 @@ | ||
| 113 | 113 | [/help?cmd=ui|UI], |
| 114 | 114 | protected by [./caps/ | a fine-grained role-based |
| 115 | 115 | access control system]. |
| 116 | 116 | These additional capabilities are available for Git as 3rd-party |
| 117 | 117 | add-ons, but with Fossil they are integrated into |
| 118 | -the design. One way to describe Fossil is that it is | |
| 118 | +the design, to the point that it approximates | |
| 119 | 119 | "[https://github.com/ | GitHub]-in-a-box." |
| 120 | 120 | |
| 121 | -Fossil can do operations over all local repo clones and check-out | |
| 122 | -directories with a single command. For example, Fossil lets you say | |
| 123 | -"<tt>fossil all sync</tt>" on a laptop prior to taking it off the network | |
| 124 | -hosting those repos. You can sync up to all of the private repos on your | |
| 125 | -company network plus those public Internet-hosted repos you use. Whether | |
| 126 | -going out for a working lunch or on a transoceanic airplane trip, one | |
| 127 | -command gets you in sync. This works with several other Fossil | |
| 128 | -sub-commands, such as "<tt>fossil all changes</tt>" to get a list of files | |
| 121 | +Even if you only want straight version control, Fossil has affordances | |
| 122 | +not available in Git. | |
| 123 | + | |
| 124 | +For instance, Fossil can do operations over all local repo clones and | |
| 125 | +check-out directories with a single command. You can say "<tt>fossil | |
| 126 | +all sync</tt>" on a laptop prior to taking it off the network hosting | |
| 127 | +those repos, as before going on a trip. It doesn't matter if those | |
| 128 | +repos are private and restricted to your company network or public | |
| 129 | +Internet-hosted repos, you get synced up with everything you need while | |
| 130 | +off-network. | |
| 131 | + | |
| 132 | +You get the same capability with several other Fossil | |
| 133 | +sub-commands as well, such as "<tt>fossil all changes</tt>" to get a list of files | |
| 129 | 134 | that you forgot to commit prior to the end of your working day, across |
| 130 | 135 | all repos. |
| 131 | 136 | |
| 132 | 137 | Whenever Fossil is told to modify the local checkout in some destructive |
| 133 | 138 | way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update], |
| 134 | 139 | [/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state |
| 135 | 140 | and is able to return the check-out directory to that state with a |
| 136 | -<tt>fossil undo</tt> command. You cannot undo a commit in Fossil | |
| 137 | -([#history | on purpose!]) but as long as the change remains confined to | |
| 141 | +<tt>fossil undo</tt> command. While you cannot undo a commit in Fossil | |
| 142 | +— [#history | on purpose!] — as long as the change remains confined to | |
| 138 | 143 | the local check-out directory only, Fossil makes undo |
| 139 | 144 | [https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in |
| 140 | 145 | Git]. |
| 141 | 146 | |
| 142 | -For developers who choose to self-host projects (rather than using a | |
| 143 | -3rd-party service such as GitHub) Fossil is much easier to set up, since | |
| 147 | +For developers who choose to self-host projects rather than rely on a | |
| 148 | +3rd-party service such as GitHub, Fossil is much easier to set up: | |
| 144 | 149 | the stand-alone Fossil executable together with a [./server/any/cgi.md|2-line CGI script] |
| 145 | 150 | suffice to instantiate a full-featured developer website. To accomplish |
| 146 | 151 | the same using Git requires locating, installing, configuring, integrating, |
| 147 | 152 | and managing a wide assortment of separate tools. Standing up a developer |
| 148 | 153 | website using Fossil can be done in minutes, whereas doing the same using |
| @@ -165,27 +170,38 @@ | ||
| 165 | 170 | so that most people end up installing it via some kind of package |
| 166 | 171 | manager, simply because the creation of complicated binary packages is |
| 167 | 172 | best delegated to people skilled in their creation. Normal Git users are |
| 168 | 173 | not expected to build Git from source and install it themselves. |
| 169 | 174 | |
| 170 | -Fossil is a single self-contained stand-alone executable which by default | |
| 171 | -depends only on common platform libraries. You can statically link | |
| 172 | -to get an executable with no external dependencies at all — a useful | |
| 173 | -feature for running inside a restrictive | |
| 174 | -[https://en.wikipedia.org/wiki/Chroot|chroot jail]. | |
| 175 | - | |
| 176 | -The precompiled Fossil binaries are delivered as just a single | |
| 177 | -executable. The precompiled Windows deliveries are just a ZIP archive | |
| 175 | +Fossil is a single self-contained stand-alone executable which | |
| 176 | +depends only on common platform libraries in its default configuration. | |
| 177 | +To install one of [https://fossil-scm.org/home/uv/download.html | our | |
| 178 | +precompiled binaries], unpack the executable from the archive and put it | |
| 179 | +somewhere in your <tt>PATH</tt>. To uninstall it, delete the executable. | |
| 180 | + | |
| 181 | +This policy is particularly useful when running Fossil inside a | |
| 182 | +restrictive container, anything from [./chroot.md | classic chroot | |
| 183 | +jails] to modern [https://en.wikipedia.org/wiki/OS-level_virtualization | |
| 184 | +| OS-level virtualization mechanisms] such as | |
| 185 | +[https://en.wikipedia.org/wiki/Docker_(software) | Docker]. Our | |
| 186 | +[/file?name=Dockerfile&ci=trunk | stock <tt>Dockerfile</tt>] | |
| 187 | +creates a container that's under 9 MiB on 64-bit Linux, including | |
| 188 | +a capable [https://www.busybox.net/ | Busybox] environment for live | |
| 189 | +debugging of the container's innards. | |
| 190 | + | |
| 191 | +Modern Linux systems tend to make full static linking | |
| 192 | +[https://stackoverflow.com/questions/3430400/linux-static-linking-is-dead | |
| 193 | +| difficult], but our official executables do statically link to OpenSSL | |
| 194 | +to remove a version dependency, resulting in an executable that's around | |
| 195 | +6 MiB, depending on the platform. ([Release Build How-To | Details].) | |
| 196 | +The result is dependent only upon widespread platform libraries with | |
| 197 | +stable ABIs such as glibc, zlib, etc. | |
| 198 | + | |
| 199 | +Full static linking is easier on Windows, so our precompiled Windows | |
| 200 | +binaries are just a ZIP archive | |
| 178 | 201 | containing only "<tt>fossil.exe</tt>". There is no "<tt>setup.exe</tt>" |
| 179 | -to run. Linux and Mac precompiled binaries are a tarball containing | |
| 180 | -just the "<tt>fossil</tt>" executable. To install, just put the | |
| 181 | -executable on your PATH. To uninstall, just delete the executable. | |
| 182 | -To upgrade (or downgrade) simply replace the executable. | |
| 183 | - | |
| 184 | -A typical Fossil executable is between 5 and 7 megabytes uncompressed | |
| 185 | -(as of 2020-12-12), | |
| 186 | -assuming that the executable is statically linked against OpenSSL. | |
| 202 | +to run. | |
| 187 | 203 | |
| 188 | 204 | Fossil is easy to build from sources. Just run |
| 189 | 205 | "<tt>./configure && make</tt>" on POSIX systems and |
| 190 | 206 | "<tt>nmake /f Makefile.msc</tt>" on Windows. |
| 191 | 207 | |
| @@ -208,18 +224,20 @@ | ||
| 208 | 224 | matters is effectiveness and efficiency. We believe Fossil achieves |
| 209 | 225 | this. |
| 210 | 226 | |
| 211 | 227 | The above size comparisons aren't apples-to-apples anyway. We've |
| 212 | 228 | compared the size of Fossil with all of its [#features | many built-in |
| 213 | -features] to a fairly minimal Git installation. You must add a lot | |
| 214 | -of third-party | |
| 215 | -software to Git to give it a Fossil-equivalent feature set. Consider | |
| 216 | -[https://about.gitlab.com/|GitLab], a third-party extension to Git | |
| 217 | -wrapping it in many features, making it roughly Fossil-equivalent, | |
| 229 | +features] to a fairly minimal Git installation. You must add a lot of | |
| 230 | +third-party software to Git to give it a Fossil-equivalent feature set. | |
| 231 | +Consider [https://about.gitlab.com/|GitLab], a third-party extension to | |
| 232 | +Git wrapping it in many features, making it roughly Fossil-equivalent, | |
| 218 | 233 | though [https://docs.gitlab.com/ee/install/requirements.html|much more |
| 219 | -resource hungry] and hence more costly to run than the equivalent | |
| 220 | -Fossil setup. GitLab's basic requirements are easy to accept when you're dedicating | |
| 234 | +resource hungry] and hence more costly to run than the equivalent Fossil | |
| 235 | +setup. [https://hub.docker.com/r/gitlab/gitlab-ce/ | The official GitLab | |
| 236 | +Community Edition container] currently clocks in at 2.66 GiB! | |
| 237 | + | |
| 238 | +GitLab's requirements are easy to accept when you're dedicating | |
| 221 | 239 | a local rack server or blade to it, since its minimum requirements are |
| 222 | 240 | more or less a description of the smallest |
| 223 | 241 | thing you could call a "server" these days, but when you go to host that |
| 224 | 242 | in the cloud, you can expect to pay about 8 times as much to comfortably host |
| 225 | 243 | GitLab as for Fossil.³ This difference is largely due to basic |
| 226 | 244 |
| --- www/fossil-v-git.wiki | |
| +++ www/fossil-v-git.wiki | |
| @@ -113,36 +113,41 @@ | |
| 113 | [/help?cmd=ui|UI], |
| 114 | protected by [./caps/ | a fine-grained role-based |
| 115 | access control system]. |
| 116 | These additional capabilities are available for Git as 3rd-party |
| 117 | add-ons, but with Fossil they are integrated into |
| 118 | the design. One way to describe Fossil is that it is |
| 119 | "[https://github.com/ | GitHub]-in-a-box." |
| 120 | |
| 121 | Fossil can do operations over all local repo clones and check-out |
| 122 | directories with a single command. For example, Fossil lets you say |
| 123 | "<tt>fossil all sync</tt>" on a laptop prior to taking it off the network |
| 124 | hosting those repos. You can sync up to all of the private repos on your |
| 125 | company network plus those public Internet-hosted repos you use. Whether |
| 126 | going out for a working lunch or on a transoceanic airplane trip, one |
| 127 | command gets you in sync. This works with several other Fossil |
| 128 | sub-commands, such as "<tt>fossil all changes</tt>" to get a list of files |
| 129 | that you forgot to commit prior to the end of your working day, across |
| 130 | all repos. |
| 131 | |
| 132 | Whenever Fossil is told to modify the local checkout in some destructive |
| 133 | way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update], |
| 134 | [/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state |
| 135 | and is able to return the check-out directory to that state with a |
| 136 | <tt>fossil undo</tt> command. You cannot undo a commit in Fossil |
| 137 | ([#history | on purpose!]) but as long as the change remains confined to |
| 138 | the local check-out directory only, Fossil makes undo |
| 139 | [https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in |
| 140 | Git]. |
| 141 | |
| 142 | For developers who choose to self-host projects (rather than using a |
| 143 | 3rd-party service such as GitHub) Fossil is much easier to set up, since |
| 144 | the stand-alone Fossil executable together with a [./server/any/cgi.md|2-line CGI script] |
| 145 | suffice to instantiate a full-featured developer website. To accomplish |
| 146 | the same using Git requires locating, installing, configuring, integrating, |
| 147 | and managing a wide assortment of separate tools. Standing up a developer |
| 148 | website using Fossil can be done in minutes, whereas doing the same using |
| @@ -165,27 +170,38 @@ | |
| 165 | so that most people end up installing it via some kind of package |
| 166 | manager, simply because the creation of complicated binary packages is |
| 167 | best delegated to people skilled in their creation. Normal Git users are |
| 168 | not expected to build Git from source and install it themselves. |
| 169 | |
| 170 | Fossil is a single self-contained stand-alone executable which by default |
| 171 | depends only on common platform libraries. You can statically link |
| 172 | to get an executable with no external dependencies at all — a useful |
| 173 | feature for running inside a restrictive |
| 174 | [https://en.wikipedia.org/wiki/Chroot|chroot jail]. |
| 175 | |
| 176 | The precompiled Fossil binaries are delivered as just a single |
| 177 | executable. The precompiled Windows deliveries are just a ZIP archive |
| 178 | containing only "<tt>fossil.exe</tt>". There is no "<tt>setup.exe</tt>" |
| 179 | to run. Linux and Mac precompiled binaries are a tarball containing |
| 180 | just the "<tt>fossil</tt>" executable. To install, just put the |
| 181 | executable on your PATH. To uninstall, just delete the executable. |
| 182 | To upgrade (or downgrade) simply replace the executable. |
| 183 | |
| 184 | A typical Fossil executable is between 5 and 7 megabytes uncompressed |
| 185 | (as of 2020-12-12), |
| 186 | assuming that the executable is statically linked against OpenSSL. |
| 187 | |
| 188 | Fossil is easy to build from sources. Just run |
| 189 | "<tt>./configure && make</tt>" on POSIX systems and |
| 190 | "<tt>nmake /f Makefile.msc</tt>" on Windows. |
| 191 | |
| @@ -208,18 +224,20 @@ | |
| 208 | matters is effectiveness and efficiency. We believe Fossil achieves |
| 209 | this. |
| 210 | |
| 211 | The above size comparisons aren't apples-to-apples anyway. We've |
| 212 | compared the size of Fossil with all of its [#features | many built-in |
| 213 | features] to a fairly minimal Git installation. You must add a lot |
| 214 | of third-party |
| 215 | software to Git to give it a Fossil-equivalent feature set. Consider |
| 216 | [https://about.gitlab.com/|GitLab], a third-party extension to Git |
| 217 | wrapping it in many features, making it roughly Fossil-equivalent, |
| 218 | though [https://docs.gitlab.com/ee/install/requirements.html|much more |
| 219 | resource hungry] and hence more costly to run than the equivalent |
| 220 | Fossil setup. GitLab's basic requirements are easy to accept when you're dedicating |
| 221 | a local rack server or blade to it, since its minimum requirements are |
| 222 | more or less a description of the smallest |
| 223 | thing you could call a "server" these days, but when you go to host that |
| 224 | in the cloud, you can expect to pay about 8 times as much to comfortably host |
| 225 | GitLab as for Fossil.³ This difference is largely due to basic |
| 226 |
| --- www/fossil-v-git.wiki | |
| +++ www/fossil-v-git.wiki | |
| @@ -113,36 +113,41 @@ | |
| 113 | [/help?cmd=ui|UI], |
| 114 | protected by [./caps/ | a fine-grained role-based |
| 115 | access control system]. |
| 116 | These additional capabilities are available for Git as 3rd-party |
| 117 | add-ons, but with Fossil they are integrated into |
| 118 | the design, to the point that it approximates |
| 119 | "[https://github.com/ | GitHub]-in-a-box." |
| 120 | |
| 121 | Even if you only want straight version control, Fossil has affordances |
| 122 | not available in Git. |
| 123 | |
| 124 | For instance, Fossil can do operations over all local repo clones and |
| 125 | check-out directories with a single command. You can say "<tt>fossil |
| 126 | all sync</tt>" on a laptop prior to taking it off the network hosting |
| 127 | those repos, as before going on a trip. It doesn't matter if those |
| 128 | repos are private and restricted to your company network or public |
| 129 | Internet-hosted repos, you get synced up with everything you need while |
| 130 | off-network. |
| 131 | |
| 132 | You get the same capability with several other Fossil |
| 133 | sub-commands as well, such as "<tt>fossil all changes</tt>" to get a list of files |
| 134 | that you forgot to commit prior to the end of your working day, across |
| 135 | all repos. |
| 136 | |
| 137 | Whenever Fossil is told to modify the local checkout in some destructive |
| 138 | way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update], |
| 139 | [/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state |
| 140 | and is able to return the check-out directory to that state with a |
| 141 | <tt>fossil undo</tt> command. While you cannot undo a commit in Fossil |
| 142 | — [#history | on purpose!] — as long as the change remains confined to |
| 143 | the local check-out directory only, Fossil makes undo |
| 144 | [https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in |
| 145 | Git]. |
| 146 | |
| 147 | For developers who choose to self-host projects rather than rely on a |
| 148 | 3rd-party service such as GitHub, Fossil is much easier to set up: |
| 149 | the stand-alone Fossil executable together with a [./server/any/cgi.md|2-line CGI script] |
| 150 | suffice to instantiate a full-featured developer website. To accomplish |
| 151 | the same using Git requires locating, installing, configuring, integrating, |
| 152 | and managing a wide assortment of separate tools. Standing up a developer |
| 153 | website using Fossil can be done in minutes, whereas doing the same using |
| @@ -165,27 +170,38 @@ | |
| 170 | so that most people end up installing it via some kind of package |
| 171 | manager, simply because the creation of complicated binary packages is |
| 172 | best delegated to people skilled in their creation. Normal Git users are |
| 173 | not expected to build Git from source and install it themselves. |
| 174 | |
| 175 | Fossil is a single self-contained stand-alone executable which |
| 176 | depends only on common platform libraries in its default configuration. |
| 177 | To install one of [https://fossil-scm.org/home/uv/download.html | our |
| 178 | precompiled binaries], unpack the executable from the archive and put it |
| 179 | somewhere in your <tt>PATH</tt>. To uninstall it, delete the executable. |
| 180 | |
| 181 | This policy is particularly useful when running Fossil inside a |
| 182 | restrictive container, anything from [./chroot.md | classic chroot |
| 183 | jails] to modern [https://en.wikipedia.org/wiki/OS-level_virtualization |
| 184 | | OS-level virtualization mechanisms] such as |
| 185 | [https://en.wikipedia.org/wiki/Docker_(software) | Docker]. Our |
| 186 | [/file?name=Dockerfile&ci=trunk | stock <tt>Dockerfile</tt>] |
| 187 | creates a container that's under 9 MiB on 64-bit Linux, including |
| 188 | a capable [https://www.busybox.net/ | Busybox] environment for live |
| 189 | debugging of the container's innards. |
| 190 | |
| 191 | Modern Linux systems tend to make full static linking |
| 192 | [https://stackoverflow.com/questions/3430400/linux-static-linking-is-dead |
| 193 | | difficult], but our official executables do statically link to OpenSSL |
| 194 | to remove a version dependency, resulting in an executable that's around |
| 195 | 6 MiB, depending on the platform. ([Release Build How-To | Details].) |
| 196 | The result is dependent only upon widespread platform libraries with |
| 197 | stable ABIs such as glibc, zlib, etc. |
| 198 | |
| 199 | Full static linking is easier on Windows, so our precompiled Windows |
| 200 | binaries are just a ZIP archive |
| 201 | containing only "<tt>fossil.exe</tt>". There is no "<tt>setup.exe</tt>" |
| 202 | to run. |
| 203 | |
| 204 | Fossil is easy to build from sources. Just run |
| 205 | "<tt>./configure && make</tt>" on POSIX systems and |
| 206 | "<tt>nmake /f Makefile.msc</tt>" on Windows. |
| 207 | |
| @@ -208,18 +224,20 @@ | |
| 224 | matters is effectiveness and efficiency. We believe Fossil achieves |
| 225 | this. |
| 226 | |
| 227 | The above size comparisons aren't apples-to-apples anyway. We've |
| 228 | compared the size of Fossil with all of its [#features | many built-in |
| 229 | features] to a fairly minimal Git installation. You must add a lot of |
| 230 | third-party software to Git to give it a Fossil-equivalent feature set. |
| 231 | Consider [https://about.gitlab.com/|GitLab], a third-party extension to |
| 232 | Git wrapping it in many features, making it roughly Fossil-equivalent, |
| 233 | though [https://docs.gitlab.com/ee/install/requirements.html|much more |
| 234 | resource hungry] and hence more costly to run than the equivalent Fossil |
| 235 | setup. [https://hub.docker.com/r/gitlab/gitlab-ce/ | The official GitLab |
| 236 | Community Edition container] currently clocks in at 2.66 GiB! |
| 237 | |
| 238 | GitLab's requirements are easy to accept when you're dedicating |
| 239 | a local rack server or blade to it, since its minimum requirements are |
| 240 | more or less a description of the smallest |
| 241 | thing you could call a "server" these days, but when you go to host that |
| 242 | in the cloud, you can expect to pay about 8 times as much to comfortably host |
| 243 | GitLab as for Fossil.³ This difference is largely due to basic |
| 244 |