diff --git a/cgit.mk b/cgit.mk index e582ca3..566a2ad 100644 --- a/cgit.mk +++ b/cgit.mk @@ -96,6 +96,7 @@ CGIT_OBJ_NAMES += ui-tag.o CGIT_OBJ_NAMES += ui-tree.o CGIT_OBJ_NAMES += themed/themed.o +CGIT_OBJ_NAMES += themed/mincrypt_sha256.o CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES)) @@ -128,7 +129,7 @@ $(CGIT_PREFIX).depend: $(CGIT_PREFIX)themed/.depend: @mkdir -p $@ -$(CGIT_PREFIX)themed/themed.c: $(CGIT_PREFIX)themed/index.html +$(CGIT_PREFIX)themed/themed.c: $(CGIT_PREFIX)themed/base.html $(CGIT_PREFIX)themed/index.html $(CGIT_PREFIX)themed/refs.html cd $(CGIT_PREFIX)themed; python -m htmlcc $^ > $@ $(CGIT_PREFIX)CGIT-CFLAGS: FORCE diff --git a/themed/base.html b/themed/base.html new file mode 100644 index 0000000..5905df7 --- /dev/null +++ b/themed/base.html @@ -0,0 +1,114 @@ +{% block page_start %} + + + + + {{ ctx.page.title }}{# ctx.page.title is usually set by prepare_repo_cmd #} + + + + +{% endblock %} +{% block repo_header %} +
{# Repo header #} +
+ {# Heroicons outline cube #} + + +
+
+{% endblock %} +{% block page_end %} + + + +{% endblock %} + +{! + static int get_num_commits() { + int num_commits = 0; + + // Based on ui-stats collect_stats + struct rev_info rev; + struct commit *commit; + + const char *argv[] = {NULL, ctx.qry.head}; + int argc = 2; + + repo_init_revisions(the_repository, &rev, NULL); + rev.abbrev = DEFAULT_ABBREV; + rev.commit_format = CMIT_FMT_DEFAULT; + rev.max_parents = 1; + rev.verbose_header = 1; + rev.show_root_diff = 0; + setup_revisions(argc, argv, &rev, NULL); + prepare_revision_walk(&rev); + while ((commit = get_revision(&rev)) != NULL) { + // Process the commit + num_commits++; + + release_commit_memory(the_repository->parsed_objects, commit); + commit->parents = NULL; + } + return num_commits; + } + + static int get_num_branches() { + struct reflist list; + list.refs = NULL; + list.alloc = list.count = 0; + refs_for_each_branch_ref(get_main_ref_store(the_repository), cgit_refs_cb, &list); + int num_branches = list.count; + cgit_free_reflist_inner(&list); + return num_branches; + } + + static int get_num_tags() { + struct reflist list; + list.refs = NULL; + list.alloc = list.count = 0; + refs_for_each_tag_ref(get_main_ref_store(the_repository), cgit_refs_cb, &list); + int num_tags = list.count; + cgit_free_reflist_inner(&list); + return num_tags; + } +!} + +{! + #include "mincrypt_sha256.h" + + static void gravatar_url(char *email) { + // Trim email of whitespace, < and > + while (isspace(*email) || *email == '<') email++; + char* strend = email + strlen(email) - 1; + while (isspace(*strend) || *strend == '>') strend--; // Now strend points to the last character + + // Email to lowercase + size_t email_len = strend - email + 1; + char *email_lower = malloc(email_len + 1); // +1 for null terminator + for (int i = 0; i < email_len; i++) { email_lower[i] = tolower(email[i]); } + email_lower[email_len] = '\0'; // For good measure + + // Compute hash and print Gravatar link + uint8_t digest[32]; + SHA256_hash(email, strend - email + 1, digest); + htmlf( + "https://gravatar.com/avatar/%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], + digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15], + digest[16], digest[17], digest[18], digest[19], digest[20], digest[21], digest[22], digest[23], + digest[24], digest[25], digest[26], digest[27], digest[28], digest[29], digest[30], digest[31] + ); + + free(email_lower); + } +!} diff --git a/themed/index.html b/themed/index.html index 6933898..6e88d43 100644 --- a/themed/index.html +++ b/themed/index.html @@ -4,24 +4,16 @@ {% page cgit_print_repolist %} {! ctx.page.title = ctx.cfg.root_title; !} - - - - - {{ ctx.cfg.root_title }} - - - - +{! page_start(); !}
{# Repo header #}
- {{ ctx.cfg.root_title }} + {{ ctx.cfg.root_title }}
{# Main content #} -
+ {# Search box #}
- - - +{! page_end(); !} {% endpage %} diff --git a/themed/mincrypt_sha256.c b/themed/mincrypt_sha256.c new file mode 100644 index 0000000..540e1a1 --- /dev/null +++ b/themed/mincrypt_sha256.c @@ -0,0 +1,184 @@ +/* sha256.c +** +** Copyright 2013, The Android Open Source Project +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of Google Inc. nor the names of its contributors may +** be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Optimized for minimal code size. + +#include "mincrypt_sha256.h" + +#include +#include +#include + +#define ror(value, bits) (((value) >> (bits)) | ((value) << (32 - (bits)))) +#define shr(value, bits) ((value) >> (bits)) + +static const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; + +static void SHA256_Transform(mincrypt_SHA256_CTX* ctx) { + uint32_t W[64]; + uint32_t A, B, C, D, E, F, G, H; + uint8_t* p = ctx->buf; + int t; + + for(t = 0; t < 16; ++t) { + uint32_t tmp = *p++ << 24; + tmp |= *p++ << 16; + tmp |= *p++ << 8; + tmp |= *p++; + W[t] = tmp; + } + + for(; t < 64; t++) { + uint32_t s0 = ror(W[t-15], 7) ^ ror(W[t-15], 18) ^ shr(W[t-15], 3); + uint32_t s1 = ror(W[t-2], 17) ^ ror(W[t-2], 19) ^ shr(W[t-2], 10); + W[t] = W[t-16] + s0 + W[t-7] + s1; + } + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + F = ctx->state[5]; + G = ctx->state[6]; + H = ctx->state[7]; + + for(t = 0; t < 64; t++) { + uint32_t s0 = ror(A, 2) ^ ror(A, 13) ^ ror(A, 22); + uint32_t maj = (A & B) ^ (A & C) ^ (B & C); + uint32_t t2 = s0 + maj; + uint32_t s1 = ror(E, 6) ^ ror(E, 11) ^ ror(E, 25); + uint32_t ch = (E & F) ^ ((~E) & G); + uint32_t t1 = H + s1 + ch + K[t] + W[t]; + + H = G; + G = F; + F = E; + E = D + t1; + D = C; + C = B; + B = A; + A = t1 + t2; + } + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; + ctx->state[5] += F; + ctx->state[6] += G; + ctx->state[7] += H; +} + +static const HASH_VTAB SHA256_VTAB = { + SHA256_init, + SHA256_update, + SHA256_final, + SHA256_hash, + SHA256_DIGEST_SIZE +}; + +void SHA256_init(mincrypt_SHA256_CTX* ctx) { + ctx->f = &SHA256_VTAB; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; + ctx->count = 0; +} + + +void SHA256_update(mincrypt_SHA256_CTX* ctx, const void* data, int len) { + int i = (int) (ctx->count & 63); + const uint8_t* p = (const uint8_t*)data; + + ctx->count += len; + + while (len--) { + ctx->buf[i++] = *p++; + if (i == 64) { + SHA256_Transform(ctx); + i = 0; + } + } +} + + +const uint8_t* SHA256_final(mincrypt_SHA256_CTX* ctx) { + uint8_t *p = ctx->buf; + uint64_t cnt = ctx->count * 8; + int i; + + SHA256_update(ctx, (uint8_t*)"\x80", 1); + while ((ctx->count & 63) != 56) { + SHA256_update(ctx, (uint8_t*)"\0", 1); + } + for (i = 0; i < 8; ++i) { + uint8_t tmp = (uint8_t) (cnt >> ((7 - i) * 8)); + SHA256_update(ctx, &tmp, 1); + } + + for (i = 0; i < 8; i++) { + uint32_t tmp = ctx->state[i]; + *p++ = tmp >> 24; + *p++ = tmp >> 16; + *p++ = tmp >> 8; + *p++ = tmp >> 0; + } + + return ctx->buf; +} + +/* Convenience function */ +const uint8_t* SHA256_hash(const void* data, int len, uint8_t* digest) { + mincrypt_SHA256_CTX ctx; + SHA256_init(&ctx); + SHA256_update(&ctx, data, len); + memcpy(digest, SHA256_final(&ctx), SHA256_DIGEST_SIZE); + return digest; +} diff --git a/themed/mincrypt_sha256.h b/themed/mincrypt_sha256.h new file mode 100644 index 0000000..f2dc24f --- /dev/null +++ b/themed/mincrypt_sha256.h @@ -0,0 +1,115 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Google Inc. nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_ +#define SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct HASH_CTX; // forward decl + +typedef struct HASH_VTAB { + void (* const init)(struct HASH_CTX*); + void (* const update)(struct HASH_CTX*, const void*, int); + const uint8_t* (* const final)(struct HASH_CTX*); + const uint8_t* (* const hash)(const void*, int, uint8_t*); + int size; +} HASH_VTAB; + +typedef struct HASH_CTX { + const HASH_VTAB * f; + uint64_t count; + uint8_t buf[64]; + uint32_t state[8]; // upto SHA2 +} HASH_CTX; + +#define HASH_init(ctx) (ctx)->f->init(ctx) +#define HASH_update(ctx, data, len) (ctx)->f->update(ctx, data, len) +#define HASH_final(ctx) (ctx)->f->final(ctx) +#define HASH_hash(data, len, digest) (ctx)->f->hash(data, len, digest) +#define HASH_size(ctx) (ctx)->f->size + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_ + +/* + * Copyright 2011 The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Google Inc. nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_ +#define SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef HASH_CTX mincrypt_SHA256_CTX; + +void SHA256_init(mincrypt_SHA256_CTX* ctx); +void SHA256_update(mincrypt_SHA256_CTX* ctx, const void* data, int len); +const uint8_t* SHA256_final(mincrypt_SHA256_CTX* ctx); + +// Convenience method. Returns digest address. +const uint8_t* SHA256_hash(const void* data, int len, uint8_t* digest); + +#define SHA256_DIGEST_SIZE 32 + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_ diff --git a/themed/refs.html b/themed/refs.html new file mode 100644 index 0000000..d0ef6fd --- /dev/null +++ b/themed/refs.html @@ -0,0 +1,190 @@ +{! int cgit_refs_cmp_branch_age(const void *a, const void *b); !} +{! int cgit_refs_cmp_ref_name(const void *a, const void *b); !} +{! int cgit_refs_cmp_tag_age(const void *a, const void *b); !} + +{% page cgit_print_refs %} +{! page_start(); !} +{! repo_header(); !} +
{# Main content #} +
+ {# Description panel #} + {{ ctx.repo->desc }} +
+ + {% if !ctx.qry.path || starts_with(ctx.qry.path, "heads") %} + {! + struct reflist list; + list.refs = NULL; + list.alloc = list.count = 0; + refs_for_each_branch_ref(get_main_ref_store(the_repository), cgit_refs_cb, &list); + qsort(list.refs, list.count, sizeof(*list.refs), cgit_refs_cmp_branch_age); + if (ctx.repo->branch_sort == 0) { + qsort(list.refs, list.count, sizeof(*list.refs), cgit_refs_cmp_ref_name); + } + !} +
+ {# Branches list #} +
+ Branches +
+ {% for int i = 0; i < list.count; i++ %} + {! struct refinfo *ref = list.refs[i]; !} + {! struct commitinfo *info = ref->commit; !} + {! char *name = (char *)ref->refname; !} + {! if (!info) { continue; } !} +
+ + {% if ref->object->type == OBJ_COMMIT %} +
+ {{ info->subject }} · Updated {! cgit_print_age(info->committer_date, info->committer_tz, -1); !} ago + + {{ info->author }} +
+ {% endif %} +
+ {% endfor %} +
+ {! cgit_free_reflist_inner(&list); !} + {% endif %} + {% if !ctx.qry.path || starts_with(ctx.qry.path, "tags") %} + {! + struct reflist list; + list.refs = NULL; + list.alloc = list.count = 0; + refs_for_each_tag_ref(get_main_ref_store(the_repository), cgit_refs_cb, &list); + !} + {% if list.count > 0 %} + {! qsort(list.refs, list.count, sizeof(*list.refs), cgit_refs_cmp_tag_age); !} +
+ {# Tags list #} +
+ Tags +
+ {% for int i = 0; i < list.count; i++ %} + {! + struct refinfo *ref = list.refs[i]; + struct tag *tag = NULL; + struct taginfo *info = NULL; + char *name = (char *)ref->refname; + struct object *obj = ref->object; + + if (obj->type == OBJ_TAG) { + tag = (struct tag *)obj; + obj = tag->tagged; + info = ref->tag; + if (!info) { continue; } + } + !} +
+ + {% if info && (info->tagger_date > 0 || info->tagger) %} +
+ {% if info->tagger_date > 0 %} + Updated {! cgit_print_age(info->tagger_date, info->tagger_tz, -1); !} ago + {% endif %} + {% if info->tagger %} + + {{ info->tagger }} + {% endif %} +
+ {% elif ref->object->type == OBJ_COMMIT %} +
+ Updated {! cgit_print_age(ref->commit->commit->date, 0, -1); !} ago + + {{ ref->commit->author }} +
+ {% endif %} +
+ {% endfor %} +
+ {! cgit_free_reflist_inner(&list); !} + {% endif %}{# if list.count > 0 #} + {% endif %}{# if !ctx.qry.path || starts_with(ctx.qry.path, "tags") #} +
+{! page_end(); !} +{% endpage %} diff --git a/ui-refs.c b/ui-refs.c index 11fb9fc..54b8283 100644 --- a/ui-refs.c +++ b/ui-refs.c @@ -27,6 +27,11 @@ static int cmp_ref_name(const void *a, const void *b) return strcmp(r1->refname, r2->refname); } +int cgit_refs_cmp_ref_name(const void *a, const void *b) +{ + return cmp_ref_name(a, b); +} + static int cmp_branch_age(const void *a, const void *b) { struct refinfo *r1 = *(struct refinfo **)a; @@ -35,6 +40,11 @@ static int cmp_branch_age(const void *a, const void *b) return cmp_age(r1->commit->committer_date, r2->commit->committer_date); } +int cgit_refs_cmp_branch_age(const void *a, const void *b) +{ + return cmp_branch_age(a, b); +} + static int get_ref_age(struct refinfo *ref) { if (!ref->object) @@ -56,6 +66,11 @@ static int cmp_tag_age(const void *a, const void *b) return cmp_age(get_ref_age(r1), get_ref_age(r2)); } +int cgit_refs_cmp_tag_age(const void *a, const void *b) +{ + return cmp_tag_age(a, b); +} + static int print_branch(struct refinfo *ref) { struct commitinfo *info = ref->commit; @@ -205,7 +220,7 @@ void cgit_print_tags(int maxcount) cgit_free_reflist_inner(&list); } -void cgit_print_refs(void) +void _orig_cgit_print_refs(void) { cgit_print_layout_start(); html(""); diff --git a/ui-refs.h b/ui-refs.h index 1d4a54a..c2ab68a 100644 --- a/ui-refs.h +++ b/ui-refs.h @@ -1,6 +1,10 @@ #ifndef UI_REFS_H #define UI_REFS_H +extern int cgit_refs_cmp_branch_age(const void *a, const void *b); +extern int cgit_refs_cmp_ref_name(const void *a, const void *b); +extern int cgit_refs_cmp_tag_age(const void *a, const void *b); + extern void cgit_print_branches(int maxcount); extern void cgit_print_tags(int maxcount); extern void cgit_print_refs(void); diff --git a/ui-shared.c b/ui-shared.c index 5853a96..8922e7d 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -318,6 +318,43 @@ static char *repolink(const char *title, const char *class, const char *page, return fmt("%s", delim); } +void cgit_shared_repolink_url(const char *page, const char *head, const char *path) +{ + char *delim = "?"; + + if (ctx.cfg.virtual_root) { + html_url_path(ctx.cfg.virtual_root); + html_url_path(ctx.repo->url); + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') + html("/"); + if (page) { + html_url_path(page); + html("/"); + if (path) + html_url_path(path); + } + } else { + html_url_path(ctx.cfg.script_name); + html("?url="); + html_url_arg(ctx.repo->url); + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') + html("/"); + if (page) { + html_url_arg(page); + html("/"); + if (path) + html_url_arg(path); + } + delim = "&"; + } + if (head && ctx.repo->defbranch && strcmp(head, ctx.repo->defbranch)) { + html(delim); + html("h="); + html_url_arg(head); + delim = "&"; + } +} + static void reporevlink(const char *page, const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) diff --git a/ui-shared.h b/ui-shared.h index ce05a9b..8f96c28 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -1,6 +1,7 @@ #ifndef UI_SHARED_H #define UI_SHARED_H +extern void cgit_shared_repolink_url(const char *page, const char *head, const char *path); extern void cgit_shared_site_url(const char *page, const char *search, const char *sort, int ofs, int always_root); extern const char *cgit_httpscheme(void);