~sircmpwn/sr.ht-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
61 2

[RFC PATCH git.sr.ht v1 0/8] Arbitrary default branches

Details
Message ID
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
DKIM signature
pass
Download raw message
There's a few distinct parts to this:
  1-4. disentangling special semantics given to "master"
  5-7. establishing default-branch semantics
  8  . switching default branches from the Web UI

Part A is relatively straightforward, but does require injecting
default_branch into the templates and straightening out inconsistencies
WRT what "default_branch" is (from now on it's a pygit2.Branch).

Part B defines a simple mechanism for setting default branches:
if HEAD is dangling post-update, find the first of
[branches just pushed, any extant branches], and set it to that,
this Does-What-I-Mean (and, hopefully, what most users would mean)
in the majority of cases, and allows us to preserve a more pervasive
no-default-branch being equivalent to empty-repo semantic
(confer patch 6).

Please note that this is my first time writing Go, and, though I tried
my best, I expect it to be less than ideal.

Part C is trivial implementation-wise due to the setup in parts A and B,
but I couldn't for the life of me figure out where to put the form
button for it to not be ugly as ‒ I'm hoping for a designer's direxion
here as well.

The changes are also available to be fetched from the Git repository at
  https://git.sr.ht/~nabijaczleweli/git.sr.ht no-gods-no-masters-v1
up to d7af8dedb45bbfdfc2de7b5003af7292314ba331.

наб (8):
  Handle non-'master' default branches in util.html#breadcrumb()
  Return the right non-'master' default-branch link in RSS feeds
  Pass down the default branch to log_rss_url()
  Use the default branch for <meta name="go-source"> links
  Extract ANSI escapes to constants
  Add default-branch-updating logic to post-update hook
  Make Repository.default_branch() simply read the default branch
  Allow setting the default branch from the refs view

 gitsrht-update-hook/post-update.go | 65 +++++++++++++++++++++++++++---
 gitsrht/blueprints/artifacts.py    |  5 ++-
 gitsrht/blueprints/repo.py         | 41 ++++++++++++++-----
 gitsrht/blueprints/stats.py        |  3 +-
 gitsrht/git.py                     |  9 +----
 gitsrht/templates/blob.html        |  2 +-
 gitsrht/templates/log.html         |  5 ++-
 gitsrht/templates/refs.html        | 20 ++++++++-
 gitsrht/templates/repo.html        | 12 ++++--
 gitsrht/templates/tree.html        |  2 +-
 gitsrht/templates/utils.html       |  5 +--
 gitsrht/urls.py                    |  4 +-
 12 files changed, 133 insertions(+), 40 deletions(-)

-- 
2.20.1

[RFC PATCH git.sr.ht v1 1/8] Handle non-'master' default branches in util.html#breadcrumb()

Details
Message ID
<48c933204f592347b334d3642f45ca451de23017.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +8 -7
---
 gitsrht/blueprints/repo.py   | 6 ++++--
 gitsrht/templates/blob.html  | 2 +-
 gitsrht/templates/tree.html  | 2 +-
 gitsrht/templates/utils.html | 5 ++---
 4 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 9672ffb..873e505 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -236,7 +236,8 @@ def tree(owner, repo, ref, path):
                        blob=blob, data=data, commit=orig_commit,
                        highlight_file=_highlight_file,
                        editorconfig=editorconfig,
                        markdown=md, force_source=force_source)
                        markdown=md, force_source=force_source,
                        default_branch=git_repo.default_branch())
            tree = git_repo.get(entry.id)

        if not tree:
@@ -245,7 +246,8 @@ def tree(owner, repo, ref, path):
        tree = sorted(tree, key=lambda e: e.name)

        return render_template("tree.html", view="tree", owner=owner, repo=repo,
                ref=ref, commit=commit, tree=tree, path=path)
                ref=ref, commit=commit, tree=tree, path=path,
                default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/blob/<path:ref>/<path:path>")
def raw_blob(owner, repo, ref, path):
diff --git a/gitsrht/templates/blob.html b/gitsrht/templates/blob.html
index 2531f72..6c426b3 100644
--- a/gitsrht/templates/blob.html
+++ b/gitsrht/templates/blob.html
@@ -16,7 +16,7 @@ pre, body {
<div class="header-extension" style="margin-bottom: 0;">
  <div class="blob container-fluid">
    <span>
      {{ utils.breadcrumb(ref, repo, path, path_join) }}
      {{ utils.breadcrumb(ref, repo, default_branch, path, path_join) }}
      <span class="text-muted" style="margin-left: 1rem">
        <span title="{{"{0:0o}".format(entry.filemode)}}">
          {{stat.filemode(entry.filemode)}}
diff --git a/gitsrht/templates/tree.html b/gitsrht/templates/tree.html
index e9de2ad..57ea3ce 100644
--- a/gitsrht/templates/tree.html
+++ b/gitsrht/templates/tree.html
@@ -7,7 +7,7 @@
<div class="header-extension">
  <div class="container">
    <span style="padding-left: 1rem">
      {{ utils.breadcrumb(ref, repo, path, path_join) }}
      {{ utils.breadcrumb(ref, repo, default_branch, path, path_join) }}
    </span>
    <div class="pull-right">
      <a
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index dad52be..4a52cb5 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -1,6 +1,5 @@
{% macro breadcrumb(ref, repo, path, path_join) %}
{# TODO: Default branches other than master #}
{% if ref != "master" %}
{% macro breadcrumb(ref, repo, default_branch, path, path_join) %}
{% if ref != default_branch.name[("refs/heads/"|length):] %}
<span style="margin-right: 1rem">
  <span class="text-muted">ref:</span> {{ ref }}
</span>
-- 
2.20.1

[PATCH git.sr.ht v1 2/8] Return the right non-'master' default-branch link in RSS feeds

Details
Message ID
<059b7cc92a3e73e4f1970111f73660bdbc7ce37f.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -1
---
 gitsrht/blueprints/repo.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 873e505..6f35b5c 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -391,6 +391,7 @@ def log_rss(owner, repo, ref):
        if not isinstance(commit, pygit2.Commit):
            abort(404)
        commits = get_log(git_repo, commit)
        default_branch = git_repo.default_branch().name[len("refs/heads/"):]

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
@@ -398,7 +399,7 @@ def log_rss(owner, repo, ref):
    link = cfg("git.sr.ht", "origin") + url_for("repo.log",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=ref if ref != "master" else None)
        ref=ref if ref != default_branch else None)

    return generate_feed(repo, commits, title, link, description)

-- 
2.20.1

[RFC PATCH git.sr.ht v1 3/8] Pass down the default branch to log_rss_url()

Details
Message ID
<ed7f8fbb738f4218e4880b229c4ab2bb7dd15b72.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +7 -5
---
 gitsrht/blueprints/repo.py | 3 ++-
 gitsrht/templates/log.html | 5 +++--
 gitsrht/urls.py            | 4 ++--
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 6f35b5c..fe90ae0 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -379,7 +379,8 @@ def log(owner, repo, ref, path):

        return render_template("log.html", view="log",
                owner=owner, repo=repo, ref=ref, path=path,
                commits=commits, refs=refs)
                commits=commits, refs=refs,
                default_branch=git_repo.default_branch())


@repo.route("/<owner>/<repo>/log/rss.xml", defaults={"ref": None})
diff --git a/gitsrht/templates/log.html b/gitsrht/templates/log.html
index fa618cf..c338872 100644
--- a/gitsrht/templates/log.html
+++ b/gitsrht/templates/log.html
@@ -8,13 +8,14 @@
  <link rel="alternate"
    title="{{ repo.owner.canonical_name }}/{{ repo.name }}: {{ ref }} log"
    type="application/rss+xml"
    href="{{ root }}{{ repo|log_rss_url(ref=ref) }}">
    href="{{ root }}{{ repo|log_rss_url(ref=ref, default_branch=default_branch) }}">
{% endblock %}

{% block tabs_extra %}
  <li class="flex-grow-1 d-none d-sm-block"></li>
  <li class="nav-item d-none d-sm-block">
    <a class="nav-link active" href="{{ repo|log_rss_url(ref=ref) }}">
    <a class="nav-link active"
       href="{{ repo|log_rss_url(ref=ref, default_branch=default_branch) }}">
      {{ icon('rss', cls='sm') }} RSS
    </a>
  </li>
diff --git a/gitsrht/urls.py b/gitsrht/urls.py
index b3384e7..f6b6859 100644
--- a/gitsrht/urls.py
+++ b/gitsrht/urls.py
@@ -12,8 +12,8 @@ def clone_urls(repo):
        for url in ["https://{}/{}/{}", git_user+"@{}:{}/{}"]
    ]

def log_rss_url(repo, ref=None):
    ref = ref if ref != "master" else None
def log_rss_url(repo, ref=None, default_branch=None):
    ref = ref if ref != default_branch.name[len("refs/heads/"):] else None
    return url_for("repo.log_rss",
        owner=repo.owner.canonical_name,
        repo=repo.name,
-- 
2.20.1

[RFC PATCH git.sr.ht v1 4/8] Use the default branch for <meta name="go-source"> links

Details
Message ID
<211a19049379f6650b68285c6ba244a8651151c2.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +20 -12
---
These are all files that inherit from repo.html

 gitsrht/blueprints/artifacts.py |  5 +++--
 gitsrht/blueprints/repo.py      | 12 +++++++-----
 gitsrht/blueprints/stats.py     |  3 ++-
 gitsrht/templates/repo.html     | 12 ++++++++----
 4 files changed, 20 insertions(+), 12 deletions(-)

diff --git a/gitsrht/blueprints/artifacts.py b/gitsrht/blueprints/artifacts.py
index e02d68b..cece6af 100644
--- a/gitsrht/blueprints/artifacts.py
+++ b/gitsrht/blueprints/artifacts.py
@@ -41,15 +41,16 @@ def ref_upload(owner, repo, ref):
        valid = Validation(request)
        f = request.files.get("file")
        valid.expect(f, "File is required", field="file")
        default_branch = git_repo.default_branch()
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        artifact = upload_artifact(valid, repo, target, f, f.filename)
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        db.session.commit()
        return redirect(url_for("repo.ref",
            owner=owner.canonical_name,
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index fe90ae0..7d42bd3 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -422,7 +422,8 @@ def commit(owner, repo, ref):
        return render_template("commit.html", view="log",
            owner=owner, repo=repo, ref=ref, refs=refs,
            commit=commit, parent=parent,
            diff=diff, diffstat=diffstat, pygit2=pygit2)
            diff=diff, diffstat=diffstat, pygit2=pygit2,
            default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/commit/<path:ref>.patch")
def patch(owner, repo, ref):
@@ -474,9 +475,9 @@ def refs(owner, repo):
                git_repo.branches[branch],
                git_repo.get(git_repo.branches[branch].target)
            ) for branch in git_repo.branches.local]
        default_branch = git_repo.default_branch().name
        default_branch = git_repo.default_branch()
        branches = sorted(branches,
                key=lambda b: (b[1].name == default_branch, b[2].commit_time),
                key=lambda b: (b[1].name == default_branch.name, b[2].commit_time),
                reverse=True)

        results_per_page = 10
@@ -498,7 +499,8 @@ def refs(owner, repo):
        return render_template("refs.html", view="refs",
                owner=owner, repo=repo, tags=tags, branches=branches,
                git_repo=git_repo, isinstance=isinstance, pygit2=pygit2,
                page=page + 1, total_pages=total_pages)
                page=page + 1, total_pages=total_pages,
                default_branch=default_branch)


@repo.route("/<owner>/<repo>/refs/rss.xml")
@@ -545,4 +547,4 @@ def ref(owner, repo, ref):
                .filter(Artifact.commit == tag.target.hex)).all()
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                artifacts=artifacts)
                artifacts=artifacts, default_branch=git_repo.default_branch())
diff --git a/gitsrht/blueprints/stats.py b/gitsrht/blueprints/stats.py
index 90bef8f..e883048 100644
--- a/gitsrht/blueprints/stats.py
+++ b/gitsrht/blueprints/stats.py
@@ -38,4 +38,5 @@ def contributors(owner, repo):
        chart_data = get_contrib_chart_data(contributions)

    return render_template("contributors.html", view="contributors",
        owner=owner, repo=repo, chart_data=chart_data)
        owner=owner, repo=repo, chart_data=chart_data,
        default_branch=default_branch)
diff --git a/gitsrht/templates/repo.html b/gitsrht/templates/repo.html
index 7f662b7..c99ee11 100644
--- a/gitsrht/templates/repo.html
+++ b/gitsrht/templates/repo.html
@@ -6,10 +6,14 @@
{# Man, this is lame #}
<meta name="go-import"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} git {{(repo | clone_urls)[0]}}">
<meta name="go-source"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}/{file}#L{line}">
{% if default_branch %}
  <meta name="go-source"
    content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}/{file}#L{line}">
{% endif %}
{% endblock %}
{% block body %}
<div class="header-tabbed">
-- 
2.20.1

[RFC PATCH git.sr.ht v1 5/8] Extract ANSI escapes to constants

Details
Message ID
<4613f021736e897e3d6080b392c3203bdb26d273.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +11 -5
---
 gitsrht-update-hook/post-update.go | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index 3a0d1b8..0a9fcb0 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -16,12 +16,18 @@ import (
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

const ansi_reset  = "\033[0m"
const ansi_bold   = "\033[1m"
const ansi_notice = "\033[93m"  // bright yellow
const ansi_url    = "\033[94m"  // bright blue

func printAutocreateInfo(context PushContext) {
	log.Println("\n\t\033[93mNOTICE\033[0m")
	log.Printf("\n\t%sNOTICE%s", ansi_notice, ansi_reset)
	log.Println("\tWe saved your changes, but this repository does not exist.")
	log.Println("\tClick here to create it:")
	log.Println()
	log.Printf("\t%s/create?name=%s", origin, context.Repo.Name)
	log.Printf("\t%s%s/create?name=%s%s",
		         ansi_url, origin, context.Repo.Name, ansi_reset)
	log.Println()
	log.Println("\tYour changes will be discarded in 20 minutes.")
	log.Println()
@@ -252,14 +258,14 @@ func postUpdate() {
			if len(results) == 0 {
				continue
			} else if len(results) == 1 {
				log.Println("\033[1mBuild started:\033[0m")
				log.Printf("%sBuild started:%s", ansi_bold, ansi_reset)
			} else {
				log.Println("\033[1mBuilds started:\033[0m")
				log.Printf("%sBuilds started:%s", ansi_bold, ansi_reset)
			}
			logger.Printf("Submitted %d builds for %s",
				len(results), refname)
			for _, result := range results {
				log.Printf("\033[94m%s\033[0m [%s]", result.Url, result.Name)
				log.Printf("%s%s%s [%s]", ansi_url, result.Url, ansi_reset, result.Name)
			}
			nbuilds += len(results)
		}
-- 
2.20.1

[RFC PATCH git.sr.ht v1 6/8] Add default-branch-updating logic to post-update hook

Details
Message ID
<a7a5dfcd66cf3fa2206cea6e34dd9657c619705e.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +49 -0
If the default branch doesn't exist, it will now try to pick another
branch from this push; if none exist, it will set it to the first branch
in iteration order

This, for the very most part, DWIMs, is a no-op for repos with "master"
branches, and a one-time first-push update for repos that use a
different default name

Similarly, "git push origin :trunk trunk:new-trunk" should behave
as expected, changing the default branch from trunk to new-trunk
---
gitsrht-update-hook/post-update.go | 49 ++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)

diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index 0a9fcb0..d5af435 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -14,6 +14,7 @@ import (
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

const ansi_reset  = "\033[0m"
@@ -271,6 +272,54 @@ func postUpdate() {
		}
	}

	// Check if HEAD's dangling (i.e. the default branch doesn't exist)
	// if so, try to find a branch from this push to set as the default
	// if none were found, set the first branch in iteration order as default
	head, err := repo.Reference("HEAD", false)
	if err != nil {
		logger.Fatalf("repo.Reference(\"HEAD\"): %v", err)
	}

	var head_dangling bool = false;
	_, err = repo.Reference(head.Target(), false)
	if err != nil {
		head_dangling = true
	}

	if head_dangling {
		log.Printf("%sDefault branch:%s", ansi_bold, ansi_reset)
		log.Printf("Dangling at %s%s%s",
		           ansi_url, head.Target()[len("refs/heads/"):], ansi_reset)

		cbk := func (ref *plumbing.Reference) error {
			if ref == nil {
				return nil
			}

			log.Printf("Updating to %s%s%s",
			           ansi_url, ansi_reset, ref.Name()[len("refs/heads/"):])
			repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", ref.Name()))
			head_dangling = false
			return storer.ErrStop
		}
		for _, ref_n := range refs {
			if !strings.HasPrefix(ref_n, "refs/heads/") {
				continue
			}

			ref, _ := repo.Reference(plumbing.ReferenceName(ref_n), false)
			if cbk(ref) != nil {
				break
			}
		}

		if head_dangling {
			if branches, _ := repo.Branches(); branches != nil {
				branches.ForEach(cbk)
			}
		}
	}

	payloadBytes, err := json.Marshal(&payload)
	if err != nil {
		logger.Fatalf("Failed to marshal webhook payload: %v", err)
-- 
2.20.1

[RFC PATCH git.sr.ht v1 7/8] Make Repository.default_branch() simply read the default branch

Details
Message ID
<e0ee78e5e683c4f5ac5aff372f952b15e4eedca5.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -7
Alongside the previous commit this preserves the semantic of
"default_branch() == None" being equivalent to "repository empty",
but that is now managed by the post-update hook
---
 gitsrht/git.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/gitsrht/git.py b/gitsrht/git.py
index d87a07f..a197bac 100644
--- a/gitsrht/git.py
+++ b/gitsrht/git.py
@@ -52,13 +52,8 @@ class Repository(GitRepository):
        return super().get(ref)

    def default_branch(self):
        branch = self.branches.get("master")
        if not branch:
            if not any(self.branches.local):
                return None
            branch = list(self.branches.local)[0]
            branch = self.branches.get(branch)
        return branch
        head_ref = self.lookup_reference("HEAD")
        return self.branches.get(head_ref.target[len("refs/heads/"):])

    @property
    def is_empty(self):
-- 
2.20.1

[RFC PATCH git.sr.ht v1 8/8] Allow setting the default branch from the refs view

Details
Message ID
<d7af8dedb45bbfdfc2de7b5003af7292314ba331.1595538899.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +34 -3
---

Notes:
    The circle-notch-only button is because I have no idea how to lay this out. Suggestions?

 gitsrht/blueprints/repo.py  | 17 ++++++++++++++++-
 gitsrht/templates/refs.html | 20 ++++++++++++++++++--
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 7d42bd3..0fce96c 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -18,11 +18,12 @@ from jinja2 import Markup
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
from scmsrht.access import get_repo, get_repo_or_redir
from scmsrht.access import check_access, get_repo, get_repo_or_redir, UserAccess
from scmsrht.formatting import get_formatted_readme, get_highlighted_file
from scmsrht.urls import get_clone_urls
from srht.config import cfg, get_origin
from srht.markdown import markdown
from srht.oauth import loginrequired
from urllib.parse import urlparse

repo = Blueprint('repo', __name__)
@@ -548,3 +549,17 @@ def ref(owner, repo, ref):
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                artifacts=artifacts, default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/refs/<ref>/set_default", methods=["POST"])
@loginrequired
def ref_set_default(owner, repo, ref):
    owner, repo = check_access(owner, repo, UserAccess.manage)
    with GitRepository(repo.path) as git_repo:
        new_default_branch = git_repo.branches.get(ref)
        if new_default_branch is None:
            abort(404)
        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        return redirect(url_for("repo.refs",
            owner=owner.canonical_name,
            repo=repo.name))
diff --git a/gitsrht/templates/refs.html b/gitsrht/templates/refs.html
index 8c80d95..b03c632 100644
--- a/gitsrht/templates/refs.html
+++ b/gitsrht/templates/refs.html
@@ -85,8 +85,11 @@
        {% set name = branch[0] %}
        {% set commit = branch[2] %}
        {% set branch = branch[1] %}
        {% set change_default = repo.owner == current_user and
                                branch.name != default_branch.name %}
        <div class="event">
          {{name}}

          {{ utils.commit_event(repo, commit, skip_body=True) }}
          <div class="row" style="margin-top: 0.5rem">
            <div class="col">
@@ -95,11 +98,24 @@
                  owner=repo.owner.canonical_name,
                  repo=repo.name, ref=name)}}"
                class="btn btn-block {{ "btn-primary"
                  if branch.name == git_repo.default_branch().name
                  if branch.name == default_branch.name
                  else "btn-default" }}"
              >browse {{icon("caret-right")}}</a>
            </div>
            <div class="col">
            {% if change_default %}
              <form
                method="POST"
                action="{{url_for('repo.ref_set_default',
                  owner=repo.owner.canonical_name, repo=repo.name, ref=name)}}"
                style="margin-bottom: initial;"
              >
                {{csrf_token()}}
                <button type="submit" class="btn btn-default pull-right" title="Set as default">
                  {{icon('circle-notch')}}
                </button>
              </form>
            {% endif %}
            <div class="col{{ "-md-4" if change_default else "" }}">
              <a
                href="{{url_for("repo.log",
                  owner=repo.owner.canonical_name,
-- 
2.20.1

Re: [RFC PATCH git.sr.ht v1 8/8] Allow setting the default branch from the refs view

Details
Message ID
<C4EDOYDTWVWQ.2HWOYL72SK5EI@homura>
In-Reply-To
<d7af8dedb45bbfdfc2de7b5003af7292314ba331.1595538899.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Super super fast incomplete review: this should be somewhere in the repo
settings, not on the refs page. Also, I don't know if your code handles
this, but it should guess the default repo without requiring user input
if the user pushes a repo which has no master branch.

Also, no need to put RFC on every patch, I'm going to have comments for
all of your patches regardless :)

Re: [RFC PATCH git.sr.ht v1 8/8] Allow setting the default branch from the refs view

Details
Message ID
<20200723233742.j2bdfawyutm2xddb@tarta.local.nabijaczleweli.xyz>
In-Reply-To
<C4EDOYDTWVWQ.2HWOYL72SK5EI@homura> (view parent)
DKIM signature
pass
Download raw message
On Thu, Jul 23, 2020 at 07:05:49PM -0400, Drew DeVault wrote:
> this should be somewhere in the repo settings, not on the refs page.
I was gonna put it there, but then I saw that settings are handled by
scm.sr.ht and decided to not go down that road for PoC, if only because
mercurial is an enigma to me.

I'm guessing this means I can add a git-specific settings tab in
this repo, then?

> Also, I don't know if your code handles this, but it should guess the
> default repo without requiring user input if the user pushes a repo
> which has no master branch.
(Assuming you meant "branch" by the first "repo":) yes, that's been one
of the goals; if HEAD is dangling (i.e. there's no default branch)
after a push, then it'll be updated to point at the first pushed branch;
this ensures that no matter what you use as the default branch it'll
Just Work.

> Also, no need to put RFC on every patch,
ACK, though I mostly use the RFC prefix for patchsets as a marker
(for myself mostly) that "design bad" in addition to
"implementation bad" implied by PATCH.

Re: [RFC PATCH git.sr.ht v1 8/8] Allow setting the default branch from the refs view

Details
Message ID
<C4EEDND1GZVU.1JZLXE0PYDD40@homura>
In-Reply-To
<20200723233742.j2bdfawyutm2xddb@tarta.local.nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
On Thu Jul 23, 2020 at 9:37 PM EDT, наб wrote:
> On Thu, Jul 23, 2020 at 07:05:49PM -0400, Drew DeVault wrote:
> > this should be somewhere in the repo settings, not on the refs page.
> I was gonna put it there, but then I saw that settings are handled by
> scm.sr.ht and decided to not go down that road for PoC, if only because
> mercurial is an enigma to me.
>
> I'm guessing this means I can add a git-specific settings tab in
> this repo, then?

Add it in git.sr.ht and make any necessary changes to scm.sr.ht to
support having git.sr.ht customize the settings pages.

[PATCH git.sr.ht v2 00/10] Arbitrary default branches

Details
Message ID
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<C4EEDND1GZVU.1JZLXE0PYDD40@homura> (view parent)
DKIM signature
pass
Download raw message
Patch: +101 -33
Good news: no scm.sr.ht modifications were necessary!
The design allows for very painless extension on a per-SCM basis.

Parts A and B have seen zero real changes.
Part  C was updated and now lives in "~n/r/settings/default_branch",
        with improvements to error handling stemming therefrom.

I also managed to run a full-depth test, and uncovered that
"receive.denyDeleteCurrent" needs to be set to "ignore" for git to allow
deleting the default branch, creating part D (9-10), and also finding a
minor bug in gitsrht-shell.

Note that this means that a migration of sorts is required for existing
repositories to be able to make use of some of the new semantics.
Details in patch 9 where I also provide a find(1)-based migration path,
but you may have something more sophisticated that could hook into
already in place.

наб (10):
  Handle non-'master' default branches in util.html#breadcrumb()
  Return the right non-'master' default-branch link in RSS feeds
  Pass down the default branch to log_rss_url()
  Use the default branch for <meta name="go-source"> links
  Extract ANSI escapes to constants
  Add default-branch-updating logic to post-update hook
  Make Repository.default_branch() simply read the default branch
  Allow setting the default branch from the "default branch" settings
    tab
  Set receive.denyDeleteCurrent=ignore for newly created repositories
  Also set srht.repo-id in gitsrht-shell for autocreated repositories

 gitsrht-shell/main.go                         | 10 +++
 gitsrht-update-hook/post-update.go            | 65 +++++++++++++++++--
 gitsrht/app.py                                |  2 +
 gitsrht/blueprints/artifacts.py               |  5 +-
 gitsrht/blueprints/manage.py                  | 42 ++++++++++++
 gitsrht/blueprints/repo.py                    | 24 ++++---
 gitsrht/blueprints/stats.py                   |  3 +-
 gitsrht/git.py                                |  9 +--
 gitsrht/repos.py                              |  5 ++
 gitsrht/templates/blob.html                   |  2 +-
 gitsrht/templates/log.html                    |  5 +-
 gitsrht/templates/refs.html                   |  2 +-
 gitsrht/templates/repo.html                   | 12 ++--
 .../templates/settings_default_branch.html    | 33 ++++++++++
 gitsrht/templates/settingstabs.html           |  7 ++
 gitsrht/templates/tree.html                   |  2 +-
 gitsrht/templates/utils.html                  |  5 +-
 gitsrht/urls.py                               |  4 +-
 18 files changed, 199 insertions(+), 38 deletions(-)
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_default_branch.html
 create mode 100644 gitsrht/templates/settingstabs.html

Interdiff against v1:
diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index ecf6c39..449c959 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,6 +299,16 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"srht.repo-id", strconv.Itoa(repoId)).Run(); err != nil {

					notFound("git config srht.repo-id", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config receive.denyDeleteCurrent", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {

diff --git a/gitsrht/app.py b/gitsrht/app.py
index 525e047..afafdc3 100644
--- a/gitsrht/app.py
+++ b/gitsrht/app.py
@@ -25,6 +25,7 @@ class GitApp(ScmSrhtFlask):
        from gitsrht.blueprints.api import plumbing, porcelain
        from gitsrht.blueprints.artifacts import artifacts
        from gitsrht.blueprints.email import mail
        from gitsrht.blueprints.manage import manage
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats
        from srht.graphql import gql_blueprint
@@ -32,6 +33,7 @@ class GitApp(ScmSrhtFlask):
        self.register_blueprint(plumbing)
        self.register_blueprint(porcelain)
        self.register_blueprint(mail)
        self.register_blueprint(manage)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(webhooks_notify)
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
new file mode 100644
index 0000000..21e888d
--- /dev/null
+++ b/gitsrht/blueprints/manage.py
@@ -0,0 +1,42 @@
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
from srht.oauth import loginrequired
from srht.validation import Validation
from scmsrht.access import check_access, UserAccess
from scmsrht.repos.redirect import BaseRedirectMixin

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/default_branch")
@loginrequired
def settings_default_branch(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        return redirect(url_for(".settings_default_branch",
            owner_name=owner_name, repo_name=repo.new_repo.name))
    with GitRepository(repo.path) as git_repo:
        return render_template("settings_default_branch.html",
                owner=owner, repo=repo, branches=git_repo.branches,
                default_branch_name=
                    git_repo.default_branch().name[len("refs/heads/"):])

@manage.route("/<owner_name>/<repo_name>/settings/default_branch", methods=["POST"])
@loginrequired
def settings_default_branch_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    branch = valid.require("default_branch_name", friendly_name="Default branch")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = git_repo.branches.get(branch)
        if valid.ok and new_default_branch is None:
            valid.error(f"Branch {branch} not found", field="default_branch_name")
        if not valid.ok:
            return render_template("settings_default_branch.html",
                    owner=owner, repo=repo, branches=git_repo.branches,
                    **valid.kwargs)
        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        return redirect(f"/{owner_name}/{repo_name}/settings/default_branch")
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 0fce96c..7d42bd3 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -18,12 +18,11 @@ from jinja2 import Markup
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
from scmsrht.access import check_access, get_repo, get_repo_or_redir, UserAccess
from scmsrht.access import get_repo, get_repo_or_redir
from scmsrht.formatting import get_formatted_readme, get_highlighted_file
from scmsrht.urls import get_clone_urls
from srht.config import cfg, get_origin
from srht.markdown import markdown
from srht.oauth import loginrequired
from urllib.parse import urlparse

repo = Blueprint('repo', __name__)
@@ -549,17 +548,3 @@ def ref(owner, repo, ref):
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                artifacts=artifacts, default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/refs/<ref>/set_default", methods=["POST"])
@loginrequired
def ref_set_default(owner, repo, ref):
    owner, repo = check_access(owner, repo, UserAccess.manage)
    with GitRepository(repo.path) as git_repo:
        new_default_branch = git_repo.branches.get(ref)
        if new_default_branch is None:
            abort(404)
        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        return redirect(url_for("repo.refs",
            owner=owner.canonical_name,
            repo=repo.name))
diff --git a/gitsrht/repos.py b/gitsrht/repos.py
index 1bf7b40..2ab6e46 100644
--- a/gitsrht/repos.py
+++ b/gitsrht/repos.py
@@ -90,6 +90,11 @@ class GitRepoApi(SimpleRepoApi):
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["git", "config", "srht.repo-id", str(repo.id)], check=True,
            cwd=repo.path, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        # We handle this ourselves in the post-update hook, and git's
        # default behaviour is to print a large notice and reject the push entirely
        subprocess.run(["git", "config", "receive.denyDeleteCurrent", "ignore"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["ln", "-s",
                post_update,
                os.path.join(repo.path, "hooks", "pre-receive")
diff --git a/gitsrht/templates/refs.html b/gitsrht/templates/refs.html
index b03c632..dccdea3 100644
--- a/gitsrht/templates/refs.html
+++ b/gitsrht/templates/refs.html
@@ -85,11 +85,8 @@
        {% set name = branch[0] %}
        {% set commit = branch[2] %}
        {% set branch = branch[1] %}
        {% set change_default = repo.owner == current_user and
                                branch.name != default_branch.name %}
        <div class="event">
          {{name}}

          {{ utils.commit_event(repo, commit, skip_body=True) }}
          <div class="row" style="margin-top: 0.5rem">
            <div class="col">
@@ -102,20 +99,7 @@
                  else "btn-default" }}"
              >browse {{icon("caret-right")}}</a>
            </div>
            {% if change_default %}
              <form
                method="POST"
                action="{{url_for('repo.ref_set_default',
                  owner=repo.owner.canonical_name, repo=repo.name, ref=name)}}"
                style="margin-bottom: initial;"
              >
                {{csrf_token()}}
                <button type="submit" class="btn btn-default pull-right" title="Set as default">
                  {{icon('circle-notch')}}
                </button>
              </form>
            {% endif %}
            <div class="col{{ "-md-4" if change_default else "" }}">
            <div class="col">
              <a
                href="{{url_for("repo.log",
                  owner=repo.owner.canonical_name,
diff --git a/gitsrht/templates/settings_default_branch.html b/gitsrht/templates/settings_default_branch.html
new file mode 100644
index 0000000..4f6802d
--- /dev/null
+++ b/gitsrht/templates/settings_default_branch.html
@@ -0,0 +1,33 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-12">
    <form method="POST">
      {{csrf_token()}}
      <div class="form-group">
        <label for="default_branch_name">
          Default branch
        </label>
        <select
          class="form-control {{valid.cls('default_branch_name')}}"
          id="default_branch_name"
          name="default_branch_name"
        >
          {% for branch in branches %}
            <option
              value="{{branch}}"
              {% if branch == default_branch_name %}
                selected
              {% endif %}>{{branch}}</option>
          {% endfor %}
        </select>
        {{valid.summary('default_branch_name')}}
      </div>
      <button type="submit" class="btn btn-primary pull-right">
        Set {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
diff --git a/gitsrht/templates/settingstabs.html b/gitsrht/templates/settingstabs.html
new file mode 100644
index 0000000..32c72ec
--- /dev/null
+++ b/gitsrht/templates/settingstabs.html
@@ -0,0 +1,7 @@
{% extends "bases/scmsettingstabs.html" %}

{% block extratabs %}
<li class="nav-item">
  {{ link("/default_branch", "default branch") }}
</li>
{% endblock %}
-- 
2.20.1

[PATCH git.sr.ht v2 01/10] Handle non-'master' default branches in util.html#breadcrumb()

Details
Message ID
<b4bc0ca603688c1e6c79f1b77a3c7bf58ddc5679.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +8 -7
---
 gitsrht/blueprints/repo.py   | 6 ++++--
 gitsrht/templates/blob.html  | 2 +-
 gitsrht/templates/tree.html  | 2 +-
 gitsrht/templates/utils.html | 5 ++---
 4 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 9672ffb..873e505 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -236,7 +236,8 @@ def tree(owner, repo, ref, path):
                        blob=blob, data=data, commit=orig_commit,
                        highlight_file=_highlight_file,
                        editorconfig=editorconfig,
                        markdown=md, force_source=force_source)
                        markdown=md, force_source=force_source,
                        default_branch=git_repo.default_branch())
            tree = git_repo.get(entry.id)

        if not tree:
@@ -245,7 +246,8 @@ def tree(owner, repo, ref, path):
        tree = sorted(tree, key=lambda e: e.name)

        return render_template("tree.html", view="tree", owner=owner, repo=repo,
                ref=ref, commit=commit, tree=tree, path=path)
                ref=ref, commit=commit, tree=tree, path=path,
                default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/blob/<path:ref>/<path:path>")
def raw_blob(owner, repo, ref, path):
diff --git a/gitsrht/templates/blob.html b/gitsrht/templates/blob.html
index 2531f72..6c426b3 100644
--- a/gitsrht/templates/blob.html
+++ b/gitsrht/templates/blob.html
@@ -16,7 +16,7 @@ pre, body {
<div class="header-extension" style="margin-bottom: 0;">
  <div class="blob container-fluid">
    <span>
      {{ utils.breadcrumb(ref, repo, path, path_join) }}
      {{ utils.breadcrumb(ref, repo, default_branch, path, path_join) }}
      <span class="text-muted" style="margin-left: 1rem">
        <span title="{{"{0:0o}".format(entry.filemode)}}">
          {{stat.filemode(entry.filemode)}}
diff --git a/gitsrht/templates/tree.html b/gitsrht/templates/tree.html
index e9de2ad..57ea3ce 100644
--- a/gitsrht/templates/tree.html
+++ b/gitsrht/templates/tree.html
@@ -7,7 +7,7 @@
<div class="header-extension">
  <div class="container">
    <span style="padding-left: 1rem">
      {{ utils.breadcrumb(ref, repo, path, path_join) }}
      {{ utils.breadcrumb(ref, repo, default_branch, path, path_join) }}
    </span>
    <div class="pull-right">
      <a
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index dad52be..4a52cb5 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -1,6 +1,5 @@
{% macro breadcrumb(ref, repo, path, path_join) %}
{# TODO: Default branches other than master #}
{% if ref != "master" %}
{% macro breadcrumb(ref, repo, default_branch, path, path_join) %}
{% if ref != default_branch.name[("refs/heads/"|length):] %}
<span style="margin-right: 1rem">
  <span class="text-muted">ref:</span> {{ ref }}
</span>
-- 
2.20.1

[PATCH git.sr.ht v2 02/10] Return the right non-'master' default-branch link in RSS feeds

Details
Message ID
<25a0aae549a8007b3c6acda3119ef7aa26115460.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -1
---
 gitsrht/blueprints/repo.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 873e505..6f35b5c 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -391,6 +391,7 @@ def log_rss(owner, repo, ref):
        if not isinstance(commit, pygit2.Commit):
            abort(404)
        commits = get_log(git_repo, commit)
        default_branch = git_repo.default_branch().name[len("refs/heads/"):]

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
@@ -398,7 +399,7 @@ def log_rss(owner, repo, ref):
    link = cfg("git.sr.ht", "origin") + url_for("repo.log",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=ref if ref != "master" else None)
        ref=ref if ref != default_branch else None)

    return generate_feed(repo, commits, title, link, description)

-- 
2.20.1

[PATCH git.sr.ht v2 03/10] Pass down the default branch to log_rss_url()

Details
Message ID
<6f5b93a6dbdf8f58759571f2aeb85970b69701e9.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +7 -5
---
 gitsrht/blueprints/repo.py | 3 ++-
 gitsrht/templates/log.html | 5 +++--
 gitsrht/urls.py            | 4 ++--
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 6f35b5c..fe90ae0 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -379,7 +379,8 @@ def log(owner, repo, ref, path):

        return render_template("log.html", view="log",
                owner=owner, repo=repo, ref=ref, path=path,
                commits=commits, refs=refs)
                commits=commits, refs=refs,
                default_branch=git_repo.default_branch())


@repo.route("/<owner>/<repo>/log/rss.xml", defaults={"ref": None})
diff --git a/gitsrht/templates/log.html b/gitsrht/templates/log.html
index fa618cf..c338872 100644
--- a/gitsrht/templates/log.html
+++ b/gitsrht/templates/log.html
@@ -8,13 +8,14 @@
  <link rel="alternate"
    title="{{ repo.owner.canonical_name }}/{{ repo.name }}: {{ ref }} log"
    type="application/rss+xml"
    href="{{ root }}{{ repo|log_rss_url(ref=ref) }}">
    href="{{ root }}{{ repo|log_rss_url(ref=ref, default_branch=default_branch) }}">
{% endblock %}

{% block tabs_extra %}
  <li class="flex-grow-1 d-none d-sm-block"></li>
  <li class="nav-item d-none d-sm-block">
    <a class="nav-link active" href="{{ repo|log_rss_url(ref=ref) }}">
    <a class="nav-link active"
       href="{{ repo|log_rss_url(ref=ref, default_branch=default_branch) }}">
      {{ icon('rss', cls='sm') }} RSS
    </a>
  </li>
diff --git a/gitsrht/urls.py b/gitsrht/urls.py
index b3384e7..f6b6859 100644
--- a/gitsrht/urls.py
+++ b/gitsrht/urls.py
@@ -12,8 +12,8 @@ def clone_urls(repo):
        for url in ["https://{}/{}/{}", git_user+"@{}:{}/{}"]
    ]

def log_rss_url(repo, ref=None):
    ref = ref if ref != "master" else None
def log_rss_url(repo, ref=None, default_branch=None):
    ref = ref if ref != default_branch.name[len("refs/heads/"):] else None
    return url_for("repo.log_rss",
        owner=repo.owner.canonical_name,
        repo=repo.name,
-- 
2.20.1

[PATCH git.sr.ht v2 04/10] Use the default branch for <meta name="go-source"> links

Details
Message ID
<584d6a4297c99e3fbce7a77cfae4ec4d6f6fd553.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +21 -13
---
 gitsrht/blueprints/artifacts.py |  5 +++--
 gitsrht/blueprints/repo.py      | 12 +++++++-----
 gitsrht/blueprints/stats.py     |  3 ++-
 gitsrht/templates/refs.html     |  2 +-
 gitsrht/templates/repo.html     | 12 ++++++++----
 5 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/gitsrht/blueprints/artifacts.py b/gitsrht/blueprints/artifacts.py
index e02d68b..cece6af 100644
--- a/gitsrht/blueprints/artifacts.py
+++ b/gitsrht/blueprints/artifacts.py
@@ -41,15 +41,16 @@ def ref_upload(owner, repo, ref):
        valid = Validation(request)
        f = request.files.get("file")
        valid.expect(f, "File is required", field="file")
        default_branch = git_repo.default_branch()
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        artifact = upload_artifact(valid, repo, target, f, f.filename)
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        db.session.commit()
        return redirect(url_for("repo.ref",
            owner=owner.canonical_name,
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index fe90ae0..7d42bd3 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -422,7 +422,8 @@ def commit(owner, repo, ref):
        return render_template("commit.html", view="log",
            owner=owner, repo=repo, ref=ref, refs=refs,
            commit=commit, parent=parent,
            diff=diff, diffstat=diffstat, pygit2=pygit2)
            diff=diff, diffstat=diffstat, pygit2=pygit2,
            default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/commit/<path:ref>.patch")
def patch(owner, repo, ref):
@@ -474,9 +475,9 @@ def refs(owner, repo):
                git_repo.branches[branch],
                git_repo.get(git_repo.branches[branch].target)
            ) for branch in git_repo.branches.local]
        default_branch = git_repo.default_branch().name
        default_branch = git_repo.default_branch()
        branches = sorted(branches,
                key=lambda b: (b[1].name == default_branch, b[2].commit_time),
                key=lambda b: (b[1].name == default_branch.name, b[2].commit_time),
                reverse=True)

        results_per_page = 10
@@ -498,7 +499,8 @@ def refs(owner, repo):
        return render_template("refs.html", view="refs",
                owner=owner, repo=repo, tags=tags, branches=branches,
                git_repo=git_repo, isinstance=isinstance, pygit2=pygit2,
                page=page + 1, total_pages=total_pages)
                page=page + 1, total_pages=total_pages,
                default_branch=default_branch)


@repo.route("/<owner>/<repo>/refs/rss.xml")
@@ -545,4 +547,4 @@ def ref(owner, repo, ref):
                .filter(Artifact.commit == tag.target.hex)).all()
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                artifacts=artifacts)
                artifacts=artifacts, default_branch=git_repo.default_branch())
diff --git a/gitsrht/blueprints/stats.py b/gitsrht/blueprints/stats.py
index 90bef8f..e883048 100644
--- a/gitsrht/blueprints/stats.py
+++ b/gitsrht/blueprints/stats.py
@@ -38,4 +38,5 @@ def contributors(owner, repo):
        chart_data = get_contrib_chart_data(contributions)

    return render_template("contributors.html", view="contributors",
        owner=owner, repo=repo, chart_data=chart_data)
        owner=owner, repo=repo, chart_data=chart_data,
        default_branch=default_branch)
diff --git a/gitsrht/templates/refs.html b/gitsrht/templates/refs.html
index 8c80d95..dccdea3 100644
--- a/gitsrht/templates/refs.html
+++ b/gitsrht/templates/refs.html
@@ -95,7 +95,7 @@
                  owner=repo.owner.canonical_name,
                  repo=repo.name, ref=name)}}"
                class="btn btn-block {{ "btn-primary"
                  if branch.name == git_repo.default_branch().name
                  if branch.name == default_branch.name
                  else "btn-default" }}"
              >browse {{icon("caret-right")}}</a>
            </div>
diff --git a/gitsrht/templates/repo.html b/gitsrht/templates/repo.html
index 7f662b7..c99ee11 100644
--- a/gitsrht/templates/repo.html
+++ b/gitsrht/templates/repo.html
@@ -6,10 +6,14 @@
{# Man, this is lame #}
<meta name="go-import"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} git {{(repo | clone_urls)[0]}}">
<meta name="go-source"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}/{file}#L{line}">
{% if default_branch %}
  <meta name="go-source"
    content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}/{file}#L{line}">
{% endif %}
{% endblock %}
{% block body %}
<div class="header-tabbed">
-- 
2.20.1

[PATCH git.sr.ht v2 05/10] Extract ANSI escapes to constants

Details
Message ID
<a9554c0a40f762cbbe4a073e8e55a623de1c9d9b.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +11 -5
---
 gitsrht-update-hook/post-update.go | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index 3a0d1b8..0a9fcb0 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -16,12 +16,18 @@ import (
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

const ansi_reset  = "\033[0m"
const ansi_bold   = "\033[1m"
const ansi_notice = "\033[93m"  // bright yellow
const ansi_url    = "\033[94m"  // bright blue

func printAutocreateInfo(context PushContext) {
	log.Println("\n\t\033[93mNOTICE\033[0m")
	log.Printf("\n\t%sNOTICE%s", ansi_notice, ansi_reset)
	log.Println("\tWe saved your changes, but this repository does not exist.")
	log.Println("\tClick here to create it:")
	log.Println()
	log.Printf("\t%s/create?name=%s", origin, context.Repo.Name)
	log.Printf("\t%s%s/create?name=%s%s",
		         ansi_url, origin, context.Repo.Name, ansi_reset)
	log.Println()
	log.Println("\tYour changes will be discarded in 20 minutes.")
	log.Println()
@@ -252,14 +258,14 @@ func postUpdate() {
			if len(results) == 0 {
				continue
			} else if len(results) == 1 {
				log.Println("\033[1mBuild started:\033[0m")
				log.Printf("%sBuild started:%s", ansi_bold, ansi_reset)
			} else {
				log.Println("\033[1mBuilds started:\033[0m")
				log.Printf("%sBuilds started:%s", ansi_bold, ansi_reset)
			}
			logger.Printf("Submitted %d builds for %s",
				len(results), refname)
			for _, result := range results {
				log.Printf("\033[94m%s\033[0m [%s]", result.Url, result.Name)
				log.Printf("%s%s%s [%s]", ansi_url, result.Url, ansi_reset, result.Name)
			}
			nbuilds += len(results)
		}
-- 
2.20.1

[PATCH git.sr.ht v2 06/10] Add default-branch-updating logic to post-update hook

Details
Message ID
<caa84922f428a75a71d130d139caf6a85d3e9c85.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +49 -0
If the default branch doesn't exist, it will now try to pick another
branch from this push; if none exist, it will set it to the first branch
in iteration order

This, for the very most part, DWIMs, is a no-op for repos with "master"
branches, and a one-time first-push update for repos that use a
different default name

Similarly, "git push origin :trunk trunk:new-trunk" should behave
as expected, changing the default branch from trunk to new-trunk
---
gitsrht-update-hook/post-update.go | 49 ++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)

diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index 0a9fcb0..d5af435 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -14,6 +14,7 @@ import (
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

const ansi_reset  = "\033[0m"
@@ -271,6 +272,54 @@ func postUpdate() {
		}
	}

	// Check if HEAD's dangling (i.e. the default branch doesn't exist)
	// if so, try to find a branch from this push to set as the default
	// if none were found, set the first branch in iteration order as default
	head, err := repo.Reference("HEAD", false)
	if err != nil {
		logger.Fatalf("repo.Reference(\"HEAD\"): %v", err)
	}

	var head_dangling bool = false;
	_, err = repo.Reference(head.Target(), false)
	if err != nil {
		head_dangling = true
	}

	if head_dangling {
		log.Printf("%sDefault branch:%s", ansi_bold, ansi_reset)
		log.Printf("Dangling at %s%s%s",
		           ansi_url, head.Target()[len("refs/heads/"):], ansi_reset)

		cbk := func (ref *plumbing.Reference) error {
			if ref == nil {
				return nil
			}

			log.Printf("Updating to %s%s%s",
			           ansi_url, ansi_reset, ref.Name()[len("refs/heads/"):])
			repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", ref.Name()))
			head_dangling = false
			return storer.ErrStop
		}
		for _, ref_n := range refs {
			if !strings.HasPrefix(ref_n, "refs/heads/") {
				continue
			}

			ref, _ := repo.Reference(plumbing.ReferenceName(ref_n), false)
			if cbk(ref) != nil {
				break
			}
		}

		if head_dangling {
			if branches, _ := repo.Branches(); branches != nil {
				branches.ForEach(cbk)
			}
		}
	}

	payloadBytes, err := json.Marshal(&payload)
	if err != nil {
		logger.Fatalf("Failed to marshal webhook payload: %v", err)
-- 
2.20.1

[PATCH git.sr.ht v2 07/10] Make Repository.default_branch() simply read the default branch

Details
Message ID
<3cb966ae32f08037b959231efbbe94ce675c4f4a.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -7
Alongside the previous commit this preserves the semantic of
"default_branch() == None" being equivalent to "repository empty",
but that is now managed by the post-update hook
---
 gitsrht/git.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/gitsrht/git.py b/gitsrht/git.py
index d87a07f..a197bac 100644
--- a/gitsrht/git.py
+++ b/gitsrht/git.py
@@ -52,13 +52,8 @@ class Repository(GitRepository):
        return super().get(ref)

    def default_branch(self):
        branch = self.branches.get("master")
        if not branch:
            if not any(self.branches.local):
                return None
            branch = list(self.branches.local)[0]
            branch = self.branches.get(branch)
        return branch
        head_ref = self.lookup_reference("HEAD")
        return self.branches.get(head_ref.target[len("refs/heads/"):])

    @property
    def is_empty(self):
-- 
2.20.1

[PATCH git.sr.ht v2 08/10] Allow setting the default branch from the "default branch" settings tab

Details
Message ID
<49f582af23134282d3df2ba95e93154afc8c88a9.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +84 -0
---
 gitsrht/app.py                                |  2 +
 gitsrht/blueprints/manage.py                  | 42 +++++++++++++++++++
 .../templates/settings_default_branch.html    | 33 +++++++++++++++
 gitsrht/templates/settingstabs.html           |  7 ++++
 4 files changed, 84 insertions(+)
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_default_branch.html
 create mode 100644 gitsrht/templates/settingstabs.html

diff --git a/gitsrht/app.py b/gitsrht/app.py
index 525e047..afafdc3 100644
--- a/gitsrht/app.py
+++ b/gitsrht/app.py
@@ -25,6 +25,7 @@ class GitApp(ScmSrhtFlask):
        from gitsrht.blueprints.api import plumbing, porcelain
        from gitsrht.blueprints.artifacts import artifacts
        from gitsrht.blueprints.email import mail
        from gitsrht.blueprints.manage import manage
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats
        from srht.graphql import gql_blueprint
@@ -32,6 +33,7 @@ class GitApp(ScmSrhtFlask):
        self.register_blueprint(plumbing)
        self.register_blueprint(porcelain)
        self.register_blueprint(mail)
        self.register_blueprint(manage)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(webhooks_notify)
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
new file mode 100644
index 0000000..21e888d
--- /dev/null
+++ b/gitsrht/blueprints/manage.py
@@ -0,0 +1,42 @@
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
from srht.oauth import loginrequired
from srht.validation import Validation
from scmsrht.access import check_access, UserAccess
from scmsrht.repos.redirect import BaseRedirectMixin

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/default_branch")
@loginrequired
def settings_default_branch(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        return redirect(url_for(".settings_default_branch",
            owner_name=owner_name, repo_name=repo.new_repo.name))
    with GitRepository(repo.path) as git_repo:
        return render_template("settings_default_branch.html",
                owner=owner, repo=repo, branches=git_repo.branches,
                default_branch_name=
                    git_repo.default_branch().name[len("refs/heads/"):])

@manage.route("/<owner_name>/<repo_name>/settings/default_branch", methods=["POST"])
@loginrequired
def settings_default_branch_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    branch = valid.require("default_branch_name", friendly_name="Default branch")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = git_repo.branches.get(branch)
        if valid.ok and new_default_branch is None:
            valid.error(f"Branch {branch} not found", field="default_branch_name")
        if not valid.ok:
            return render_template("settings_default_branch.html",
                    owner=owner, repo=repo, branches=git_repo.branches,
                    **valid.kwargs)
        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        return redirect(f"/{owner_name}/{repo_name}/settings/default_branch")
diff --git a/gitsrht/templates/settings_default_branch.html b/gitsrht/templates/settings_default_branch.html
new file mode 100644
index 0000000..4f6802d
--- /dev/null
+++ b/gitsrht/templates/settings_default_branch.html
@@ -0,0 +1,33 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-12">
    <form method="POST">
      {{csrf_token()}}
      <div class="form-group">
        <label for="default_branch_name">
          Default branch
        </label>
        <select
          class="form-control {{valid.cls('default_branch_name')}}"
          id="default_branch_name"
          name="default_branch_name"
        >
          {% for branch in branches %}
            <option
              value="{{branch}}"
              {% if branch == default_branch_name %}
                selected
              {% endif %}>{{branch}}</option>
          {% endfor %}
        </select>
        {{valid.summary('default_branch_name')}}
      </div>
      <button type="submit" class="btn btn-primary pull-right">
        Set {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
diff --git a/gitsrht/templates/settingstabs.html b/gitsrht/templates/settingstabs.html
new file mode 100644
index 0000000..32c72ec
--- /dev/null
+++ b/gitsrht/templates/settingstabs.html
@@ -0,0 +1,7 @@
{% extends "bases/scmsettingstabs.html" %}

{% block extratabs %}
<li class="nav-item">
  {{ link("/default_branch", "default branch") }}
</li>
{% endblock %}
-- 
2.20.1

[PATCH git.sr.ht v2 09/10] Set receive.denyDeleteCurrent=ignore for newly created repositories

Details
Message ID
<f253bffb13d625dfa79053b41dc9c2a042efecde.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +10 -0
By default, git disallows removing the current default branch, with a
large error notice like this:
-- >8 --
remote: error: By default, deleting the current branch is denied, because the next
remote: 'git clone' won't result in any file checked out, causing confusion.
remote:
remote: You can set 'receive.denyDeleteCurrent' configuration variable to
remote: 'warn' or 'ignore' in the remote repository to allow deleting the
remote: current branch, with or without a warning message.
remote:
remote: To squelch this message, you can set it to 'refuse'.
-- >8 --

However, we handle this ourselves in the post-update hook, and will only
let the default branch dangle if there are no branches (the repository
is empty), which is equivalent to the "fresh repository" state.

This also means that *all current repositories need to be migrated*
to the new config to be able to take advantage of the new semantics.

Running something like
-- >8 --
find /var/lib/git/ \
  -type d          \
  -maxdepth 2      \
  -mindepth 2      \
  -exec git -C {} config receive.denyDeleteCurrent ignore \;
-- >8 --
should do it, though it may be prudent to run this via parallel(1) or
equivalent on sites with many repositories.
---
 gitsrht-shell/main.go | 5 +++++
 gitsrht/repos.py      | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index 967b9c1..c29dc89 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,6 +299,11 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {

diff --git a/gitsrht/repos.py b/gitsrht/repos.py
index 1bf7b40..2ab6e46 100644
--- a/gitsrht/repos.py
+++ b/gitsrht/repos.py
@@ -90,6 +90,11 @@ class GitRepoApi(SimpleRepoApi):
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["git", "config", "srht.repo-id", str(repo.id)], check=True,
            cwd=repo.path, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        # We handle this ourselves in the post-update hook, and git's
        # default behaviour is to print a large notice and reject the push entirely
        subprocess.run(["git", "config", "receive.denyDeleteCurrent", "ignore"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["ln", "-s",
                post_update,
                os.path.join(repo.path, "hooks", "pre-receive")
-- 
2.20.1

[PATCH git.sr.ht v2 10/10] Also set srht.repo-id in gitsrht-shell for autocreated repositories

Details
Message ID
<32a3f3cec6792bd873355b2b4b457db62c520b47.1595602474.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +6 -1
This now mirrors gitsrht/repos.py, as it should have all along
---
 gitsrht-shell/main.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index c29dc89..449c959 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,10 +299,15 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"srht.repo-id", strconv.Itoa(repoId)).Run(); err != nil {

					notFound("git config srht.repo-id", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config", err)
					notFound("git config receive.denyDeleteCurrent", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {
-- 
2.20.1

Re: [PATCH git.sr.ht v2 01/10] Handle non-'master' default branches in util.html#breadcrumb()

Details
Message ID
<C4HMVCAMD1VI.6F5U2CXVTRVX@homura>
In-Reply-To
<b4bc0ca603688c1e6c79f1b77a3c7bf58ddc5679.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Is this necessary given that the repo object is passed all the way
through? Maybe we should add an @property to the sqlalchemy Repo type
called "git_repo" which sets up the pygit2 object for you, then utilize
that here.

Re: [PATCH git.sr.ht v2 03/10] Pass down the default branch to log_rss_url()

Details
Message ID
<C4HMW8ISK0GL.1UPPHH6NV0WUF@homura>
In-Reply-To
<6f5b93a6dbdf8f58759571f2aeb85970b69701e9.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Ditto on this patch, it shouldn't be necessary if we have the repo
object already.

Re: [PATCH git.sr.ht v2 05/10] Extract ANSI escapes to constants

Details
Message ID
<C4HMWKNXEARD.3OEXAXQ7FQ9JW@homura>
In-Reply-To
<a9554c0a40f762cbbe4a073e8e55a623de1c9d9b.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
NACK

Re: [PATCH git.sr.ht v2 06/10] Add default-branch-updating logic to post-update hook

Details
Message ID
<C4HMWNQ7217R.3QDGZ2W7WYCPN@homura>
In-Reply-To
<caa84922f428a75a71d130d139caf6a85d3e9c85.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
On Fri Jul 24, 2020 at 11:10 AM EDT, наб wrote:
> + // Check if HEAD's dangling (i.e. the default branch doesn't exist)
> + // if so, try to find a branch from this push to set as the default
> + // if none were found, set the first branch in iteration order as
> default
> + head, err := repo.Reference("HEAD", false)
> + if err != nil {
> + logger.Fatalf("repo.Reference(\"HEAD\"): %v", err)
> + }
> +
> + var head_dangling bool = false;
> + _, err = repo.Reference(head.Target(), false)
> + if err != nil {
> + head_dangling = true
> + }

Style: no semicolon after the variable declaration, use the short form,
and prefer camelCase:

	danglingHead := false

Alternatively, false is the zero value for bools, so this is also
equivalent:

	var danglingHead bool

And you can combine the reference check with the if statement:

	if _, err := repo.Reference...; err != nil {
		danglingHead = true
	}

Also, run gofmt over everything here.

> + if head_dangling {
> + log.Printf("%sDefault branch:%s", ansi_bold, ansi_reset)
> + log.Printf("Dangling at %s%s%s",
> + ansi_url, head.Target()[len("refs/heads/"):], ansi_reset)

This is confusing and too verbose, along with the rest of the logging
here. This might be suitable for the internal log but not for printing
back to the caller. It should either be silent or say something like
"The default branch for this repository has been updated to %s".

Re: [PATCH git.sr.ht v2 08/10] Allow setting the default branch from the "default branch" settings tab

Details
Message ID
<C4HMYYI6XT85.1USKKQYJMF2XE@homura>
In-Reply-To
<49f582af23134282d3df2ba95e93154afc8c88a9.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
This doesn't really need its own tab, or if it does, it should be more
general. Perhaps "git" as the tab name, implying future git-specific
settings, or putting it onto the info tab along with the
name/desc/visibility.

Re: [PATCH git.sr.ht v2 09/10] Set receive.denyDeleteCurrent=ignore for newly created repositories

Details
Message ID
<C4HMZDW93Y92.8X5YOWB4KT2Z@homura>
In-Reply-To
<f253bffb13d625dfa79053b41dc9c2a042efecde.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
This needs to be handled in an Alembic migration rather than as manual
intervention on the part of the admins.

[PATCH {git,scm}.sr.ht v3 00/10] Arbitrary default branches

Details
Message ID
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595602474.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +142 -99
ANSI escapes moved back inline.

settings_info.html got the same treatment as settingstabs.html

I didn't propagate the types.Repository.git_repo property to all users
of repo.html in patch 4, since that feels like an overzealous
pessimisation, but since git.Repository() is only constructed with
types.Repository.path, I'd thought about maybe stashing self into
types.Repository.git_repo in __init__() instead,
avoiding opening the repository twice in all cases?

The downside of course is that it requires rewriting all
git.Repository() call sites and increases complexity a bit.

наб (10):
  [git.sr.ht] Add Repository.git_repo and use it for util.html#breadcrumb()
  [git.sr.ht] Return the right non-'master' default-branch link in RSS feeds
  [git.sr.ht] Use the right default branch to log_rss_url()
  [git.sr.ht] Use the default branch for <meta name="go-source"> links
  [git.sr.ht] Add default-branch-updating logic to post-update hook
  [git.sr.ht] Make Repository.default_branch() simply read the default branch
  [git.sr.ht] Allow setting the default branch from the info settings tab
  [git.sr.ht] Set receive.denyDeleteCurrent=ignore for old and newly created repositories
  [git.sr.ht] Also set srht.repo-id in gitsrht-shell for autocreated repositories
  [scm.sr.ht] Allow extending /~n/r/settings/info in SCMs

 gitsrht-shell/main.go                         | 10 ++++
 gitsrht-update-hook/post-update.go            | 47 ++++++++++++++++++
 ...285bb23e2_allow_deleting_default_branch.py | 43 +++++++++++++++++
 gitsrht/app.py                                |  2 +
 gitsrht/blueprints/artifacts.py               |  5 +-
 gitsrht/blueprints/manage.py                  | 35 ++++++++++++++
 gitsrht/blueprints/repo.py                    | 15 +++---
 gitsrht/blueprints/stats.py                   |  3 +-
 gitsrht/git.py                                |  9 +---
 gitsrht/repos.py                              |  5 ++
 gitsrht/templates/refs.html                   |  2 +-
 gitsrht/templates/repo.html                   | 12 +++--
 gitsrht/templates/settings_info.html          | 48 +++++++++++++++++++
 gitsrht/templates/utils.html                  |  3 +-
 gitsrht/types/__init__.py                     |  9 ++++
 gitsrht/urls.py                               |  4 +-
 16 files changed, 228 insertions(+), 24 deletions(-)
 create mode 100644 gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_info.html

 scmsrht/templates/bases/scmsettings_info.html | 84 +++++++++++++++++
 scmsrht/templates/settings_info.html          | 85 +----------------
 2 files changed, 85 insertions(+), 84 deletions(-)
 create mode 100644 scmsrht/templates/bases/scmsettings_info.html

Interdiff against v2:
diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index d5af435..cf8c95d 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -17,18 +17,12 @@ import (
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

const ansi_reset  = "\033[0m"
const ansi_bold   = "\033[1m"
const ansi_notice = "\033[93m"  // bright yellow
const ansi_url    = "\033[94m"  // bright blue

func printAutocreateInfo(context PushContext) {
	log.Printf("\n\t%sNOTICE%s", ansi_notice, ansi_reset)
	log.Println("\n\t\033[93mNOTICE\033[0m")
	log.Println("\tWe saved your changes, but this repository does not exist.")
	log.Println("\tClick here to create it:")
	log.Println()
	log.Printf("\t%s%s/create?name=%s%s",
		         ansi_url, origin, context.Repo.Name, ansi_reset)
	log.Printf("\t%s/create?name=%s", origin, context.Repo.Name)
	log.Println()
	log.Println("\tYour changes will be discarded in 20 minutes.")
	log.Println()
@@ -259,14 +253,14 @@ func postUpdate() {
			if len(results) == 0 {
				continue
			} else if len(results) == 1 {
				log.Printf("%sBuild started:%s", ansi_bold, ansi_reset)
				log.Println("\033[1mBuild started:\033[0m")
			} else {
				log.Printf("%sBuilds started:%s", ansi_bold, ansi_reset)
				log.Println("\033[1mBuilds started:\033[0m")
			}
			logger.Printf("Submitted %d builds for %s",
				len(results), refname)
			for _, result := range results {
				log.Printf("%s%s%s [%s]", ansi_url, result.Url, ansi_reset, result.Name)
				log.Printf("\033[94m%s\033[0m [%s]", result.Url, result.Name)
			}
			nbuilds += len(results)
		}
@@ -280,26 +274,24 @@ func postUpdate() {
		logger.Fatalf("repo.Reference(\"HEAD\"): %v", err)
	}

	var head_dangling bool = false;
	_, err = repo.Reference(head.Target(), false)
	if err != nil {
		head_dangling = true
	danglingHead := false
	if _, err = repo.Reference(head.Target(), false); err != nil {
		danglingHead = true
	}

	if head_dangling {
		log.Printf("%sDefault branch:%s", ansi_bold, ansi_reset)
		log.Printf("Dangling at %s%s%s",
		           ansi_url, head.Target()[len("refs/heads/"):], ansi_reset)
	if danglingHead {
		logger.Printf("HEAD dangling at %s", head.Target())

		cbk := func (ref *plumbing.Reference) error {
		cbk := func(ref *plumbing.Reference) error {
			if ref == nil {
				return nil
			}

			log.Printf("Updating to %s%s%s",
			           ansi_url, ansi_reset, ref.Name()[len("refs/heads/"):])
			logger.Printf("Setting HEAD to %s", ref.Name())
			log.Printf("Default branch updated to \033[94m%s\033[0m",
				ref.Name()[len("refs/heads/"):])
			repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", ref.Name()))
			head_dangling = false
			danglingHead = false
			return storer.ErrStop
		}
		for _, ref_n := range refs {
@@ -313,7 +305,7 @@ func postUpdate() {
			}
		}

		if head_dangling {
		if danglingHead {
			if branches, _ := repo.Branches(); branches != nil {
				branches.ForEach(cbk)
			}
diff --git a/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
new file mode 100644
index 0000000..fd0c0ab
--- /dev/null
+++ b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
@@ -0,0 +1,43 @@
"""Allow deleting default branch

Revision ID: 3c1285bb23e2
Revises: 163fc2d2a2ea
Create Date: 2020-07-28 12:04:39.751225

"""

# revision identifiers, used by Alembic.
revision = '3c1285bb23e2'
down_revision = '163fc2d2a2ea'

from alembic import op
from sqlalchemy.orm import sessionmaker
from gitsrht.types import Repository
from pygit2 import Repository as GitRepository
try:
    from tqdm import tqdm
except ImportError:
    def tqdm(iterable):
        yield from iterable

Session = sessionmaker()


def upgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    print("Setting receive.denyDeleteCurrent=ignore")
    for repo in tqdm(session.query(Repository).all()):
        git_repo = GitRepository(repo.path)
        git_repo.config.set_multivar('receive.denyDeleteCurrent', '', 'ignore')


def downgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    for repo in tqdm(session.query(Repository).all()):
        git_repo = GitRepository(repo.path)
        try:
            del git_repo.config['receive.denyDeleteCurrent']
        except KeyError:
            pass
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
index 21e888d..ff84bfa 100644
--- a/gitsrht/blueprints/manage.py
+++ b/gitsrht/blueprints/manage.py
@@ -1,3 +1,4 @@
import pygit2
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
@@ -8,35 +9,27 @@ from scmsrht.repos.redirect import BaseRedirectMixin

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/default_branch")
@manage.route("/<owner_name>/<repo_name>/settings/info/default_branch", methods=["POST"])
@loginrequired
def settings_default_branch(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        return redirect(url_for(".settings_default_branch",
            owner_name=owner_name, repo_name=repo.new_repo.name))
    with GitRepository(repo.path) as git_repo:
        return render_template("settings_default_branch.html",
                owner=owner, repo=repo, branches=git_repo.branches,
                default_branch_name=
                    git_repo.default_branch().name[len("refs/heads/"):])

@manage.route("/<owner_name>/<repo_name>/settings/default_branch", methods=["POST"])
@loginrequired
def settings_default_branch_POST(owner_name, repo_name):
def settings_info_default_branch_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    branch = valid.require("default_branch_name", friendly_name="Default branch")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = git_repo.branches.get(branch)
        new_default_branch = None
        if branch:
            try:
                new_default_branch = git_repo.branches.get(branch)
            except pygit2.InvalidSpecError:
                pass
        if valid.ok and new_default_branch is None:
            valid.error(f"Branch {branch} not found", field="default_branch_name")
        if not valid.ok:
            return render_template("settings_default_branch.html",
                    owner=owner, repo=repo, branches=git_repo.branches,
                    **valid.kwargs)
            return render_template("settings_info.html",
                    owner=owner, repo=repo, **valid.kwargs)
        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        return redirect(f"/{owner_name}/{repo_name}/settings/default_branch")
        return redirect(url_for("manage.settings_info",
            owner_name=owner_name, repo_name=repo_name))
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 7d42bd3..9dc53fe 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -236,8 +234,7 @@ def tree(owner, repo, ref, path):
                        blob=blob, data=data, commit=orig_commit,
                        highlight_file=_highlight_file,
                        editorconfig=editorconfig,
                        markdown=md, force_source=force_source,
                        default_branch=git_repo.default_branch())
                        markdown=md, force_source=force_source)
            tree = git_repo.get(entry.id)

        if not tree:
@@ -246,8 +243,7 @@ def tree(owner, repo, ref, path):
        tree = sorted(tree, key=lambda e: e.name)

        return render_template("tree.html", view="tree", owner=owner, repo=repo,
                ref=ref, commit=commit, tree=tree, path=path,
                default_branch=git_repo.default_branch())
                ref=ref, commit=commit, tree=tree, path=path)

@repo.route("/<owner>/<repo>/blob/<path:ref>/<path:path>")
def raw_blob(owner, repo, ref, path):
@@ -379,8 +375,7 @@ def log(owner, repo, ref, path):

        return render_template("log.html", view="log",
                owner=owner, repo=repo, ref=ref, path=path,
                commits=commits, refs=refs,
                default_branch=git_repo.default_branch())
                commits=commits, refs=refs)


@repo.route("/<owner>/<repo>/log/rss.xml", defaults={"ref": None})
diff --git a/gitsrht/templates/blob.html b/gitsrht/templates/blob.html
index 6c426b3..2531f72 100644
--- a/gitsrht/templates/blob.html
+++ b/gitsrht/templates/blob.html
@@ -16,7 +16,7 @@ pre, body {
<div class="header-extension" style="margin-bottom: 0;">
  <div class="blob container-fluid">
    <span>
      {{ utils.breadcrumb(ref, repo, default_branch, path, path_join) }}
      {{ utils.breadcrumb(ref, repo, path, path_join) }}
      <span class="text-muted" style="margin-left: 1rem">
        <span title="{{"{0:0o}".format(entry.filemode)}}">
          {{stat.filemode(entry.filemode)}}
diff --git a/gitsrht/templates/log.html b/gitsrht/templates/log.html
index c338872..fa618cf 100644
--- a/gitsrht/templates/log.html
+++ b/gitsrht/templates/log.html
@@ -8,14 +8,13 @@
  <link rel="alternate"
    title="{{ repo.owner.canonical_name }}/{{ repo.name }}: {{ ref }} log"
    type="application/rss+xml"
    href="{{ root }}{{ repo|log_rss_url(ref=ref, default_branch=default_branch) }}">
    href="{{ root }}{{ repo|log_rss_url(ref=ref) }}">
{% endblock %}

{% block tabs_extra %}
  <li class="flex-grow-1 d-none d-sm-block"></li>
  <li class="nav-item d-none d-sm-block">
    <a class="nav-link active"
       href="{{ repo|log_rss_url(ref=ref, default_branch=default_branch) }}">
    <a class="nav-link active" href="{{ repo|log_rss_url(ref=ref) }}">
      {{ icon('rss', cls='sm') }} RSS
    </a>
  </li>
diff --git a/gitsrht/templates/settings_default_branch.html b/gitsrht/templates/settings_default_branch.html
deleted file mode 100644
index 4f6802d..0000000
--- a/gitsrht/templates/settings_default_branch.html
@@ -1,33 +0,0 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-12">
    <form method="POST">
      {{csrf_token()}}
      <div class="form-group">
        <label for="default_branch_name">
          Default branch
        </label>
        <select
          class="form-control {{valid.cls('default_branch_name')}}"
          id="default_branch_name"
          name="default_branch_name"
        >
          {% for branch in branches %}
            <option
              value="{{branch}}"
              {% if branch == default_branch_name %}
                selected
              {% endif %}>{{branch}}</option>
          {% endfor %}
        </select>
        {{valid.summary('default_branch_name')}}
      </div>
      <button type="submit" class="btn btn-primary pull-right">
        Set {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
diff --git a/gitsrht/templates/settings_info.html b/gitsrht/templates/settings_info.html
new file mode 100644
index 0000000..20466f4
--- /dev/null
+++ b/gitsrht/templates/settings_info.html
@@ -0,0 +1,48 @@
{% extends "bases/scmsettings_info.html" %}

{% block content %}
{{ super() }}

<div class="row">
  <div class="col-md-8">
    <form
      method="POST"
      action="{{url_for("manage_git.settings_info_default_branch_POST",
                  owner_name=owner.canonical_name, repo_name=repo.name)}}"
    >
      {{csrf_token()}}
      <div class="form-group">
        <label for="default_branch_name">
          Default branch
        </label>
        <select
          class="form-control {{valid.cls('default_branch_name')}}"
          id="default_branch_name"
          name="default_branch_name"
          {% if repo.git_repo.is_empty %}disabled{% endif %}
        >
          {% set default_branch_name = "" if repo.git_repo.is_empty else
                 repo.git_repo.default_branch().name[len("refs/heads/"):] %}
          {% for branch in repo.git_repo.branches %}
            <option
              value="{{branch}}"
              {% if branch == default_branch_name %}
                selected
              {% endif %}>{{branch}}</option>
          {% else %}
            <option>No branches</option>
          {% endfor %}
        </select>
        {{valid.summary('default_branch_name')}}
      </div>
      <button
        type="submit"
        class="btn btn-primary pull-right"
        {% if repo.git_repo.is_empty %}disabled{% endif %}
      >
        Set {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
diff --git a/gitsrht/templates/settingstabs.html b/gitsrht/templates/settingstabs.html
deleted file mode 100644
index 32c72ec..0000000
--- a/gitsrht/templates/settingstabs.html
@@ -1,7 +0,0 @@
{% extends "bases/scmsettingstabs.html" %}

{% block extratabs %}
<li class="nav-item">
  {{ link("/default_branch", "default branch") }}
</li>
{% endblock %}
diff --git a/gitsrht/templates/tree.html b/gitsrht/templates/tree.html
index 57ea3ce..e9de2ad 100644
--- a/gitsrht/templates/tree.html
+++ b/gitsrht/templates/tree.html
@@ -7,7 +7,7 @@
<div class="header-extension">
  <div class="container">
    <span style="padding-left: 1rem">
      {{ utils.breadcrumb(ref, repo, default_branch, path, path_join) }}
      {{ utils.breadcrumb(ref, repo, path, path_join) }}
    </span>
    <div class="pull-right">
      <a
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index 4a52cb5..9449215 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -1,5 +1,5 @@
{% macro breadcrumb(ref, repo, default_branch, path, path_join) %}
{% if ref != default_branch.name[("refs/heads/"|length):] %}
{% macro breadcrumb(ref, repo, path, path_join) %}
{% if ref != repo.git_repo.default_branch().name[("refs/heads/"|length):] %}
<span style="margin-right: 1rem">
  <span class="text-muted">ref:</span> {{ ref }}
</span>
diff --git a/gitsrht/types/__init__.py b/gitsrht/types/__init__.py
index 9a526cc..218bcbe 100644
--- a/gitsrht/types/__init__.py
+++ b/gitsrht/types/__init__.py
@@ -3,6 +3,7 @@ import sqlalchemy as sa
from sqlalchemy import event
from srht.database import Base
from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin
from gitsrht.git import Repository as GitRepository
from scmsrht.repos import BaseAccessMixin, BaseRedirectMixin
from scmsrht.repos import BaseRepositoryMixin, RepoVisibility

@@ -19,6 +20,8 @@ class Redirect(Base, BaseRedirectMixin):
    pass

class Repository(Base, BaseRepositoryMixin):
    _git_repo = None

    def update_visibility(self):
        if not os.path.exists(self.path):
            # Repo dir not initialized yet
@@ -34,6 +37,12 @@ class Repository(Base, BaseRepositoryMixin):
        elif not should_exist and os.path.exists(path):
            os.unlink(path)

    @property
    def git_repo(self):
        if not self._git_repo:
            self._git_repo = GitRepository(self.path)
        return self._git_repo

def update_visibility_event(mapper, connection, target):
    target.update_visibility()

diff --git a/gitsrht/urls.py b/gitsrht/urls.py
index f6b6859..0061750 100644
--- a/gitsrht/urls.py
+++ b/gitsrht/urls.py
@@ -1,5 +1,6 @@
from flask import url_for
from srht.config import cfg, get_origin
from gitsrht.git import Repository as GitRepository

def clone_urls(repo):
    """Returns the readonly and read/write URL for a given repo."""
@@ -12,8 +13,9 @@ def clone_urls(repo):
        for url in ["https://{}/{}/{}", git_user+"@{}:{}/{}"]
    ]

def log_rss_url(repo, ref=None, default_branch=None):
    ref = ref if ref != default_branch.name[len("refs/heads/"):] else None
def log_rss_url(repo, ref=None):
    default_branch = repo.git_repo.default_branch().name[len("refs/heads/"):]
    ref = ref if ref != default_branch else None
    return url_for("repo.log_rss",
        owner=repo.owner.canonical_name,
        repo=repo.name,
-- 
2.20.1

[PATCH git.sr.ht v3 01/09] Add Repository.git_repo and use it for util.html#breadcrumb()

Details
Message ID
<c85791756936ad70e006604c785564ee0483a4a3.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +10 -2
---
 gitsrht/templates/utils.html | 3 +--
 gitsrht/types/__init__.py    | 9 +++++++++
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index dad52be..9449215 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -1,6 +1,5 @@
{% macro breadcrumb(ref, repo, path, path_join) %}
{# TODO: Default branches other than master #}
{% if ref != "master" %}
{% if ref != repo.git_repo.default_branch().name[("refs/heads/"|length):] %}
<span style="margin-right: 1rem">
  <span class="text-muted">ref:</span> {{ ref }}
</span>
diff --git a/gitsrht/types/__init__.py b/gitsrht/types/__init__.py
index 9a526cc..218bcbe 100644
--- a/gitsrht/types/__init__.py
+++ b/gitsrht/types/__init__.py
@@ -3,6 +3,7 @@ import sqlalchemy as sa
from sqlalchemy import event
from srht.database import Base
from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin
from gitsrht.git import Repository as GitRepository
from scmsrht.repos import BaseAccessMixin, BaseRedirectMixin
from scmsrht.repos import BaseRepositoryMixin, RepoVisibility

@@ -19,6 +20,8 @@ class Redirect(Base, BaseRedirectMixin):
    pass

class Repository(Base, BaseRepositoryMixin):
    _git_repo = None

    def update_visibility(self):
        if not os.path.exists(self.path):
            # Repo dir not initialized yet
@@ -34,6 +37,12 @@ class Repository(Base, BaseRepositoryMixin):
        elif not should_exist and os.path.exists(path):
            os.unlink(path)

    @property
    def git_repo(self):
        if not self._git_repo:
            self._git_repo = GitRepository(self.path)
        return self._git_repo

def update_visibility_event(mapper, connection, target):
    target.update_visibility()

-- 
2.20.1

[PATCH git.sr.ht v3 02/09] Return the right non-'master' default-branch link in RSS feeds

Details
Message ID
<931075ff407b2d0fdc64b9a7bdf87ee3060edfee.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -1
---
 gitsrht/blueprints/repo.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 502c821..d687102 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -387,6 +387,7 @@ def log_rss(owner, repo, ref):
        if not isinstance(commit, pygit2.Commit):
            abort(404)
        commits = get_log(git_repo, commit)
        default_branch = git_repo.default_branch().name[len("refs/heads/"):]

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
@@ -394,7 +395,7 @@ def log_rss(owner, repo, ref):
    link = cfg("git.sr.ht", "origin") + url_for("repo.log",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=ref if ref != "master" else None)
        ref=ref if ref != default_branch else None)

    return generate_feed(repo, commits, title, link, description)

-- 
2.20.1

[PATCH git.sr.ht v3 03/09] Use the right default branch to log_rss_url()

Details
Message ID
<aa4286e4dbb4338bd38041f316973f23b8e01074.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +3 -1
---
 gitsrht/urls.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/gitsrht/urls.py b/gitsrht/urls.py
index b3384e7..0061750 100644
--- a/gitsrht/urls.py
+++ b/gitsrht/urls.py
@@ -1,5 +1,6 @@
from flask import url_for
from srht.config import cfg, get_origin
from gitsrht.git import Repository as GitRepository

def clone_urls(repo):
    """Returns the readonly and read/write URL for a given repo."""
@@ -13,7 +14,8 @@ def clone_urls(repo):
    ]

def log_rss_url(repo, ref=None):
    ref = ref if ref != "master" else None
    default_branch = repo.git_repo.default_branch().name[len("refs/heads/"):]
    ref = ref if ref != default_branch else None
    return url_for("repo.log_rss",
        owner=repo.owner.canonical_name,
        repo=repo.name,
-- 
2.20.1

[PATCH git.sr.ht v3 04/09] Use the default branch for <meta name="go-source"> links

Details
Message ID
<3ed098fa197501c80d3e95e2591a91787dc24da0.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +21 -13
---
 gitsrht/blueprints/artifacts.py |  5 +++--
 gitsrht/blueprints/repo.py      | 12 +++++++-----
 gitsrht/blueprints/stats.py     |  3 ++-
 gitsrht/templates/refs.html     |  2 +-
 gitsrht/templates/repo.html     | 12 ++++++++----
 5 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/gitsrht/blueprints/artifacts.py b/gitsrht/blueprints/artifacts.py
index e02d68b..cece6af 100644
--- a/gitsrht/blueprints/artifacts.py
+++ b/gitsrht/blueprints/artifacts.py
@@ -41,15 +41,16 @@ def ref_upload(owner, repo, ref):
        valid = Validation(request)
        f = request.files.get("file")
        valid.expect(f, "File is required", field="file")
        default_branch = git_repo.default_branch()
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        artifact = upload_artifact(valid, repo, target, f, f.filename)
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        db.session.commit()
        return redirect(url_for("repo.ref",
            owner=owner.canonical_name,
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index d687102..9dc53fe 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -417,7 +417,8 @@ def commit(owner, repo, ref):
        return render_template("commit.html", view="log",
            owner=owner, repo=repo, ref=ref, refs=refs,
            commit=commit, parent=parent,
            diff=diff, diffstat=diffstat, pygit2=pygit2)
            diff=diff, diffstat=diffstat, pygit2=pygit2,
            default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/commit/<path:ref>.patch")
def patch(owner, repo, ref):
@@ -469,9 +470,9 @@ def refs(owner, repo):
                git_repo.branches[branch],
                git_repo.get(git_repo.branches[branch].target)
            ) for branch in git_repo.branches.local]
        default_branch = git_repo.default_branch().name
        default_branch = git_repo.default_branch()
        branches = sorted(branches,
                key=lambda b: (b[1].name == default_branch, b[2].commit_time),
                key=lambda b: (b[1].name == default_branch.name, b[2].commit_time),
                reverse=True)

        results_per_page = 10
@@ -493,7 +494,8 @@ def refs(owner, repo):
        return render_template("refs.html", view="refs",
                owner=owner, repo=repo, tags=tags, branches=branches,
                git_repo=git_repo, isinstance=isinstance, pygit2=pygit2,
                page=page + 1, total_pages=total_pages)
                page=page + 1, total_pages=total_pages,
                default_branch=default_branch)


@repo.route("/<owner>/<repo>/refs/rss.xml")
@@ -540,4 +542,4 @@ def ref(owner, repo, ref):
                .filter(Artifact.commit == tag.target.hex)).all()
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                artifacts=artifacts)
                artifacts=artifacts, default_branch=git_repo.default_branch())
diff --git a/gitsrht/blueprints/stats.py b/gitsrht/blueprints/stats.py
index 90bef8f..e883048 100644
--- a/gitsrht/blueprints/stats.py
+++ b/gitsrht/blueprints/stats.py
@@ -38,4 +38,5 @@ def contributors(owner, repo):
        chart_data = get_contrib_chart_data(contributions)

    return render_template("contributors.html", view="contributors",
        owner=owner, repo=repo, chart_data=chart_data)
        owner=owner, repo=repo, chart_data=chart_data,
        default_branch=default_branch)
diff --git a/gitsrht/templates/refs.html b/gitsrht/templates/refs.html
index 8c80d95..dccdea3 100644
--- a/gitsrht/templates/refs.html
+++ b/gitsrht/templates/refs.html
@@ -95,7 +95,7 @@
                  owner=repo.owner.canonical_name,
                  repo=repo.name, ref=name)}}"
                class="btn btn-block {{ "btn-primary"
                  if branch.name == git_repo.default_branch().name
                  if branch.name == default_branch.name
                  else "btn-default" }}"
              >browse {{icon("caret-right")}}</a>
            </div>
diff --git a/gitsrht/templates/repo.html b/gitsrht/templates/repo.html
index 7f662b7..c99ee11 100644
--- a/gitsrht/templates/repo.html
+++ b/gitsrht/templates/repo.html
@@ -6,10 +6,14 @@
{# Man, this is lame #}
<meta name="go-import"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} git {{(repo | clone_urls)[0]}}">
<meta name="go-source"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}/{file}#L{line}">
{% if default_branch %}
  <meta name="go-source"
    content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}/{file}#L{line}">
{% endif %}
{% endblock %}
{% block body %}
<div class="header-tabbed">
-- 
2.20.1

[PATCH git.sr.ht v3 05/09] Add default-branch-updating logic to post-update hook

Details
Message ID
<5a2706619389ddf21d56acc0b6c0bf3f38a089d2.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +47 -0
If the default branch doesn't exist, it will now try to pick another
branch from this push; if none exist, it will set it to the first branch
in iteration order

This, for the very most part, DWIMs, is a no-op for repos with "master"
branches, and a one-time first-push update for repos that use a
different default name

Similarly, "git push origin :trunk trunk:new-trunk" should behave
as expected, changing the default branch from trunk to new-trunk
---
Logging reduced, internal log has one message for dangle and one for
update, user log has one message for update.

Passes gofmt, as well.

gitsrht-update-hook/post-update.go | 47 ++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)

diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index 3a0d1b8..cf8c95d 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -14,6 +14,7 @@ import (
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

func printAutocreateInfo(context PushContext) {
@@ -265,6 +266,52 @@ func postUpdate() {
		}
	}

	// Check if HEAD's dangling (i.e. the default branch doesn't exist)
	// if so, try to find a branch from this push to set as the default
	// if none were found, set the first branch in iteration order as default
	head, err := repo.Reference("HEAD", false)
	if err != nil {
		logger.Fatalf("repo.Reference(\"HEAD\"): %v", err)
	}

	danglingHead := false
	if _, err = repo.Reference(head.Target(), false); err != nil {
		danglingHead = true
	}

	if danglingHead {
		logger.Printf("HEAD dangling at %s", head.Target())

		cbk := func(ref *plumbing.Reference) error {
			if ref == nil {
				return nil
			}

			logger.Printf("Setting HEAD to %s", ref.Name())
			log.Printf("Default branch updated to \033[94m%s\033[0m",
				ref.Name()[len("refs/heads/"):])
			repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", ref.Name()))
			danglingHead = false
			return storer.ErrStop
		}
		for _, ref_n := range refs {
			if !strings.HasPrefix(ref_n, "refs/heads/") {
				continue
			}

			ref, _ := repo.Reference(plumbing.ReferenceName(ref_n), false)
			if cbk(ref) != nil {
				break
			}
		}

		if danglingHead {
			if branches, _ := repo.Branches(); branches != nil {
				branches.ForEach(cbk)
			}
		}
	}

	payloadBytes, err := json.Marshal(&payload)
	if err != nil {
		logger.Fatalf("Failed to marshal webhook payload: %v", err)
-- 
2.20.1

[PATCH git.sr.ht v3 06/09] Make Repository.default_branch() simply read the default branch

Details
Message ID
<e8682abad5a33574d55d4f6f8eea7672f24ccd9c.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -7
Alongside the previous commit this preserves the semantic of
"default_branch() == None" being equivalent to "repository empty",
but that is now managed by the post-update hook
---
 gitsrht/git.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/gitsrht/git.py b/gitsrht/git.py
index d87a07f..a197bac 100644
--- a/gitsrht/git.py
+++ b/gitsrht/git.py
@@ -52,13 +52,8 @@ class Repository(GitRepository):
        return super().get(ref)

    def default_branch(self):
        branch = self.branches.get("master")
        if not branch:
            if not any(self.branches.local):
                return None
            branch = list(self.branches.local)[0]
            branch = self.branches.get(branch)
        return branch
        head_ref = self.lookup_reference("HEAD")
        return self.branches.get(head_ref.target[len("refs/heads/"):])

    @property
    def is_empty(self):
-- 
2.20.1

[PATCH git.sr.ht v3 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<7ea6dfb0383ddc908e21958be6320a4dc7248d3c.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +85 -0
---
The form/endpoint also got hardened against the no branches and
bad branch name cases.

 gitsrht/app.py                       |  2 ++
 gitsrht/blueprints/manage.py         | 35 ++++++++++++++++++++
 gitsrht/templates/settings_info.html | 48 ++++++++++++++++++++++++++++
 3 files changed, 85 insertions(+)
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_info.html

diff --git a/gitsrht/app.py b/gitsrht/app.py
index 525e047..afafdc3 100644
--- a/gitsrht/app.py
+++ b/gitsrht/app.py
@@ -25,6 +25,7 @@ class GitApp(ScmSrhtFlask):
        from gitsrht.blueprints.api import plumbing, porcelain
        from gitsrht.blueprints.artifacts import artifacts
        from gitsrht.blueprints.email import mail
        from gitsrht.blueprints.manage import manage
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats
        from srht.graphql import gql_blueprint
@@ -32,6 +33,7 @@ class GitApp(ScmSrhtFlask):
        self.register_blueprint(plumbing)
        self.register_blueprint(porcelain)
        self.register_blueprint(mail)
        self.register_blueprint(manage)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(webhooks_notify)
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
new file mode 100644
index 0000000..ff84bfa
--- /dev/null
+++ b/gitsrht/blueprints/manage.py
@@ -0,0 +1,35 @@
import pygit2
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
from srht.oauth import loginrequired
from srht.validation import Validation
from scmsrht.access import check_access, UserAccess
from scmsrht.repos.redirect import BaseRedirectMixin

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/info/default_branch", methods=["POST"])
@loginrequired
def settings_info_default_branch_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    branch = valid.require("default_branch_name", friendly_name="Default branch")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = None
        if branch:
            try:
                new_default_branch = git_repo.branches.get(branch)
            except pygit2.InvalidSpecError:
                pass
        if valid.ok and new_default_branch is None:
            valid.error(f"Branch {branch} not found", field="default_branch_name")
        if not valid.ok:
            return render_template("settings_info.html",
                    owner=owner, repo=repo, **valid.kwargs)
        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        return redirect(url_for("manage.settings_info",
            owner_name=owner_name, repo_name=repo_name))
diff --git a/gitsrht/templates/settings_info.html b/gitsrht/templates/settings_info.html
new file mode 100644
index 0000000..20466f4
--- /dev/null
+++ b/gitsrht/templates/settings_info.html
@@ -0,0 +1,48 @@
{% extends "bases/scmsettings_info.html" %}

{% block content %}
{{ super() }}

<div class="row">
  <div class="col-md-8">
    <form
      method="POST"
      action="{{url_for("manage_git.settings_info_default_branch_POST",
                  owner_name=owner.canonical_name, repo_name=repo.name)}}"
    >
      {{csrf_token()}}
      <div class="form-group">
        <label for="default_branch_name">
          Default branch
        </label>
        <select
          class="form-control {{valid.cls('default_branch_name')}}"
          id="default_branch_name"
          name="default_branch_name"
          {% if repo.git_repo.is_empty %}disabled{% endif %}
        >
          {% set default_branch_name = "" if repo.git_repo.is_empty else
                 repo.git_repo.default_branch().name[len("refs/heads/"):] %}
          {% for branch in repo.git_repo.branches %}
            <option
              value="{{branch}}"
              {% if branch == default_branch_name %}
                selected
              {% endif %}>{{branch}}</option>
          {% else %}
            <option>No branches</option>
          {% endfor %}
        </select>
        {{valid.summary('default_branch_name')}}
      </div>
      <button
        type="submit"
        class="btn btn-primary pull-right"
        {% if repo.git_repo.is_empty %}disabled{% endif %}
      >
        Set {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
-- 
2.20.1

[PATCH git.sr.ht v3 08/09] Set receive.denyDeleteCurrent=ignore for old and newly created repositories

Details
Message ID
<2040669a7ce30d394a52f24feb0490d5e7aa1bd1.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +53 -0
By default, git disallows removing the current default branch, with a
large error notice like this:
-- >8 --
remote: error: By default, deleting the current branch is denied, because the next
remote: 'git clone' won't result in any file checked out, causing confusion.
remote:
remote: You can set 'receive.denyDeleteCurrent' configuration variable to
remote: 'warn' or 'ignore' in the remote repository to allow deleting the
remote: current branch, with or without a warning message.
remote:
remote: To squelch this message, you can set it to 'refuse'.
-- >8 --

However, we handle this ourselves in the post-update hook, and will only
let the default branch dangle if there are no branches (the repository
is empty), which is equivalent to the "fresh repository" state.

This also means that *all current repositories need to be migrated*
to the new config to be able to take advantage of the new semantics.
---
 gitsrht-shell/main.go                         |  5 +++
 ...285bb23e2_allow_deleting_default_branch.py | 43 +++++++++++++++++++
 gitsrht/repos.py                              |  5 +++
 3 files changed, 53 insertions(+)
 create mode 100644 gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py

diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index 967b9c1..c29dc89 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,6 +299,11 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {

diff --git a/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
new file mode 100644
index 0000000..fd0c0ab
--- /dev/null
+++ b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
@@ -0,0 +1,43 @@
"""Allow deleting default branch

Revision ID: 3c1285bb23e2
Revises: 163fc2d2a2ea
Create Date: 2020-07-28 12:04:39.751225

"""

# revision identifiers, used by Alembic.
revision = '3c1285bb23e2'
down_revision = '163fc2d2a2ea'

from alembic import op
from sqlalchemy.orm import sessionmaker
from gitsrht.types import Repository
from pygit2 import Repository as GitRepository
try:
    from tqdm import tqdm
except ImportError:
    def tqdm(iterable):
        yield from iterable

Session = sessionmaker()


def upgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    print("Setting receive.denyDeleteCurrent=ignore")
    for repo in tqdm(session.query(Repository).all()):
        git_repo = GitRepository(repo.path)
        git_repo.config.set_multivar('receive.denyDeleteCurrent', '', 'ignore')


def downgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    for repo in tqdm(session.query(Repository).all()):
        git_repo = GitRepository(repo.path)
        try:
            del git_repo.config['receive.denyDeleteCurrent']
        except KeyError:
            pass
diff --git a/gitsrht/repos.py b/gitsrht/repos.py
index 1bf7b40..2ab6e46 100644
--- a/gitsrht/repos.py
+++ b/gitsrht/repos.py
@@ -90,6 +90,11 @@ class GitRepoApi(SimpleRepoApi):
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["git", "config", "srht.repo-id", str(repo.id)], check=True,
            cwd=repo.path, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        # We handle this ourselves in the post-update hook, and git's
        # default behaviour is to print a large notice and reject the push entirely
        subprocess.run(["git", "config", "receive.denyDeleteCurrent", "ignore"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["ln", "-s",
                post_update,
                os.path.join(repo.path, "hooks", "pre-receive")
-- 
2.20.1

[PATCH git.sr.ht v3 09/09] Also set srht.repo-id in gitsrht-shell for autocreated repositories

Details
Message ID
<4d5eb21ac3a29ab1d362b5f9475cc7ba929fa484.1595953734.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +6 -1
This mirrors gitsrht/repos.py, as it should have all along
---
 gitsrht-shell/main.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index c29dc89..449c959 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,10 +299,15 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"srht.repo-id", strconv.Itoa(repoId)).Run(); err != nil {

					notFound("git config srht.repo-id", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config", err)
					notFound("git config receive.denyDeleteCurrent", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {
-- 
2.20.1

[PATCH scm.sr.ht v3 01/01] Allow extending /~n/r/settings/info in SCMs

Details
Message ID
<20200728164430.leuldvsgv4aq3r6d@tarta.local.nabijaczleweli.xyz>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +85 -84
---
The diff's long, but it's just a move to bases/ and an {% extends %}
in its place

 scmsrht/templates/bases/scmsettings_info.html | 84 ++++++++++++++++++
 scmsrht/templates/settings_info.html          | 85 +------------------
 2 files changed, 85 insertions(+), 84 deletions(-)
 create mode 100644 scmsrht/templates/bases/scmsettings_info.html

diff --git a/scmsrht/templates/bases/scmsettings_info.html b/scmsrht/templates/bases/scmsettings_info.html
new file mode 100644
index 0000000..2a2aba5
--- /dev/null
+++ b/scmsrht/templates/bases/scmsettings_info.html
@@ -0,0 +1,84 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-8">
    <form method="POST">
      {{csrf_token()}}
      <div class="form-group">
        <label for="name" style="display: block">
          Repository name
          <a
            href="/{{ owner.canonical_name }}/{{ repo.name }}/settings/rename"
            class="pull-right"
          >Rename?</a>
        </label>
        <input
          type="text"
          class="form-control"
          id="name"
          value="{{repo.name}}"
          readonly />
      </div>
      <div class="form-group">
        <label for="description">
          Description
        </label>
        <input
          type="text"
          class="form-control"
          id="description"
          name="description"
          value="{{repo.description}}" />
      </div>
      <fieldset class="form-group">
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Publically visible and listed on your profile"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="public"
              {{ "checked" if repo.visibility.value == "public" else "" }}
            > Public
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
              class="form-check-label"
              title="Visible to anyone with the link, but not shown on your profile"
            >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="unlisted"
              {{ "checked" if repo.visibility.value == "unlisted" else "" }}
            > Unlisted
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Only visible to you and your collaborators"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="private"
              {{ "checked" if repo.visibility.value == "private" else "" }}
            > Private
          </label>
        </div>
      </fieldset>
      <button type="submit" class="btn btn-primary pull-right">
        Save {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
diff --git a/scmsrht/templates/settings_info.html b/scmsrht/templates/settings_info.html
index 2a2aba5..da61777 100644
--- a/scmsrht/templates/settings_info.html
+++ b/scmsrht/templates/settings_info.html
@@ -1,84 +1 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-8">
    <form method="POST">
      {{csrf_token()}}
      <div class="form-group">
        <label for="name" style="display: block">
          Repository name
          <a
            href="/{{ owner.canonical_name }}/{{ repo.name }}/settings/rename"
            class="pull-right"
          >Rename?</a>
        </label>
        <input
          type="text"
          class="form-control"
          id="name"
          value="{{repo.name}}"
          readonly />
      </div>
      <div class="form-group">
        <label for="description">
          Description
        </label>
        <input
          type="text"
          class="form-control"
          id="description"
          name="description"
          value="{{repo.description}}" />
      </div>
      <fieldset class="form-group">
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Publically visible and listed on your profile"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="public"
              {{ "checked" if repo.visibility.value == "public" else "" }}
            > Public
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
              class="form-check-label"
              title="Visible to anyone with the link, but not shown on your profile"
            >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="unlisted"
              {{ "checked" if repo.visibility.value == "unlisted" else "" }}
            > Unlisted
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Only visible to you and your collaborators"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="private"
              {{ "checked" if repo.visibility.value == "private" else "" }}
            > Private
          </label>
        </div>
      </fieldset>
      <button type="submit" class="btn btn-primary pull-right">
        Save {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
{% extends "bases/scmsettings_info.html" %}
-- 
2.20.1

Re: [PATCH git.sr.ht v3 01/09] Add Repository.git_repo and use it for util.html#breadcrumb()

Details
Message ID
<C4JZ6H22MJXZ.1P9WCLZV2ZJQO@homura>
In-Reply-To
<c85791756936ad70e006604c785564ee0483a4a3.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
On Tue Jul 28, 2020 at 12:38 PM EDT, наб wrote:
> ---
> gitsrht/templates/utils.html | 3 +--
> gitsrht/types/__init__.py | 9 +++++++++
> 2 files changed, 10 insertions(+), 2 deletions(-)
>
> diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
> index dad52be..9449215 100644
> --- a/gitsrht/templates/utils.html
> +++ b/gitsrht/templates/utils.html
> @@ -1,6 +1,5 @@
> {% macro breadcrumb(ref, repo, path, path_join) %}
> -{# TODO: Default branches other than master #}
> -{% if ref != "master" %}
> +{% if ref !=
> repo.git_repo.default_branch().name[("refs/heads/"|length):] %}

This appears throughout your patchset and is not especially DRY, can you
add something like repo.git_repo.default_branch_name()?

Re: [PATCH git.sr.ht v3 05/09] Add default-branch-updating logic to post-update hook

Details
Message ID
<C4JZ80AKF8IF.3EK7ZG2TN9AH4@homura>
In-Reply-To
<5a2706619389ddf21d56acc0b6c0bf3f38a089d2.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
On Tue Jul 28, 2020 at 12:40 PM EDT, наб wrote:
> + logger.Printf("Setting HEAD to %s", ref.Name())
> + log.Printf("Default branch updated to \033[94m%s\033[0m",

Does this really need to be colored at all?

> + ref.Name()[len("refs/heads/"):])
> + repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD",
> ref.Name()))
> + danglingHead = false
> + return storer.ErrStop
> + }
> + for _, ref_n := range refs {

refName, use camelCase with Golang

Re: [PATCH git.sr.ht v3 06/09] Make Repository.default_branch() simply read the default branch

Details
Message ID
<C4JZ8UP8L4ZW.38Q0LE8FCO0ZC@homura>
In-Reply-To
<e8682abad5a33574d55d4f6f8eea7672f24ccd9c.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Hm, this makes me wonder: is it possible for HEAD to be set to a tag?
Unlikely to be a legitimate use-case but could someone do this by
mistake or maliciously? What would happen?

Re: [PATCH git.sr.ht v3 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<C4JZ9PU70TN6.0A2RZF794JLN@homura>
In-Reply-To
<7ea6dfb0383ddc908e21958be6320a4dc7248d3c.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Can you merge this into the other form? We don't need two forms on this
page.

Re: [PATCH git.sr.ht v3 08/09] Set receive.denyDeleteCurrent=ignore for old and newly created repositories

Details
Message ID
<C4JZA87185UX.3S22XKN0AEP1D@homura>
In-Reply-To
<2040669a7ce30d394a52f24feb0490d5e7aa1bd1.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
On Tue Jul 28, 2020 at 12:42 PM EDT, наб wrote:
> +def upgrade():
> + bind = op.get_bind()
> + session = Session(bind=bind)
> + print("Setting receive.denyDeleteCurrent=ignore")
> + for repo in tqdm(session.query(Repository).all()):
> + git_repo = GitRepository(repo.path)
> + git_repo.config.set_multivar('receive.denyDeleteCurrent', '',
> 'ignore')

Can you use subprocess.run here as well? Just so that it's consistent.

Re: [PATCH {git,scm}.sr.ht v3 00/10] Arbitrary default branches

Details
Message ID
<C4JZAQV9TJD8.2BAY8X5ZGK6G3@homura>
In-Reply-To
<cover.1595953734.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Thanks for your hard work, I think v4 will be the one.

Re: [PATCH git.sr.ht v3 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<20200730135622.l7motri4exrtujwy@tarta.local.nabijaczleweli.xyz>
In-Reply-To
<C4JZ9PU70TN6.0A2RZF794JLN@homura> (view parent)
DKIM signature
pass
Download raw message
On Thu, Jul 30, 2020 at 09:03:39AM -0400, Drew DeVault wrote:
> Can you merge this into the other form? We don't need two forms on this
> page.

I don't think that's possible, and if it is, I can't (think of a way to)
do it, same reason as the one for the initial
/~n/r/settings/default_branch impl: the handler for
the /~n/r/settings/info path that comes from scmsrht.manage cannot be
overriden downstream.

I guess what could be done is to override the form action to a path
managed by gitsrht? But that would probably mean either (a) duplicating
scmsrht.manage.settings_info_POST()'s logic or (b) calling it from
gitsrht's handler, which I have no idea if will work or appear to,
but be somehow broken in a nefarious way.

Re: [PATCH git.sr.ht v3 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<C4K0EIQ6X5ND.287VSORO6X64E@homura>
In-Reply-To
<20200730135622.l7motri4exrtujwy@tarta.local.nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
We could just totally override this page in git.sr.ht, reimplement the
scm.sr.ht functionality ourselves, and side-step the whole affair

Re: [PATCH git.sr.ht v3 06/09] Make Repository.default_branch() simply read the default branch

Details
Message ID
<20200730141929.42zb3ftv4qma5rqp@tarta.local.nabijaczleweli.xyz>
In-Reply-To
<C4JZ8UP8L4ZW.38Q0LE8FCO0ZC@homura> (view parent)
DKIM signature
pass
Download raw message
On Thu, Jul 30, 2020 at 09:02:31AM -0400, Drew DeVault wrote:
> Hm, this makes me wonder: is it possible for HEAD to be set to a tag?
Yes, if our server code explicitly does that, but I made sure to filter
to only branches in the two places that allow setting the HEAD
(post-update hook, manage_git.settings_info_default_branch_POST endpoint).

> Unlikely to be a legitimate use-case but could someone do this by
> mistake or maliciously?
No.

> What would happen?
The clones start out in a detached HEAD state and some slices would be
wrong, but otherwise not much.

[PATCH git.sr.ht v4 00/10] Arbitrary default branches

Details
Message ID
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<C4JZAQV9TJD8.2BAY8X5ZGK6G3@homura> (view parent)
DKIM signature
pass
Download raw message
Patch: +63 -65
The interdiff's short enough to speak for itself better than anything
I can make up about it.

The git.sr.ht changes can also be pulled from 
  https://git.sr.ht/~nabijaczleweli/git.sr.ht no-gods-no-masters-v4
up to 8749bb2f9a213861bce1e829b491bda8e3c1ce52.

наб (10):
  [git.sr.ht] Add Repository.git_repo and GitRepository.default_branch_name(), use them for util.html#breadcrumb()
  [git.sr.ht] Return the right non-'master' default-branch link in RSS feeds
  [git.sr.ht] Use the right default branch to log_rss_url()
  [git.sr.ht] Use the default branch for <meta name="go-source"> links
  [git.sr.ht] Add default-branch-updating logic to post-update hook
  [git.sr.ht] Make Repository.default_branch() simply read the default branch
  [git.sr.ht] Allow setting the default branch from the info settings tab
  [git.sr.ht] Set receive.denyDeleteCurrent=ignore for old and newly created repositories
  [git.sr.ht] Also set srht.repo-id in gitsrht-shell for autocreated repositories
  [scm.sr.ht] Allow extending /~n/r/settings/info in SCMs

git.sr.ht:
 gitsrht-shell/main.go                         | 10 ++++
 gitsrht-update-hook/post-update.go            | 46 ++++++++++++++++++
 ...285bb23e2_allow_deleting_default_branch.py | 42 ++++++++++++++++
 gitsrht/app.py                                |  2 +
 gitsrht/blueprints/artifacts.py               |  5 +-
 gitsrht/blueprints/manage.py                  | 48 +++++++++++++++++++
 gitsrht/blueprints/repo.py                    | 15 +++---
 gitsrht/blueprints/stats.py                   |  3 +-
 gitsrht/git.py                                | 16 ++++---
 gitsrht/repos.py                              |  5 ++
 gitsrht/templates/refs.html                   |  2 +-
 gitsrht/templates/repo.html                   | 12 +++--
 gitsrht/templates/settings_info.html          | 29 +++++++++++
 gitsrht/templates/utils.html                  |  3 +-
 gitsrht/types/__init__.py                     |  9 ++++
 gitsrht/urls.py                               |  2 +-
 16 files changed, 225 insertions(+), 24 deletions(-)
 create mode 100644 gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_info.html

scm.sr.ht:
 scmsrht/templates/bases/scmsettings_info.html | 85 +++++++++++++++++++
 scmsrht/templates/settings_info.html          | 85 +------------------
 2 files changed, 86 insertions(+), 84 deletions(-)
 create mode 100644 scmsrht/templates/bases/scmsettings_info.html

git.sr.ht interdiff against v3:
diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index cf8c95d..7e8989f 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -288,18 +288,17 @@ func postUpdate() {
			}

			logger.Printf("Setting HEAD to %s", ref.Name())
			log.Printf("Default branch updated to \033[94m%s\033[0m",
				ref.Name()[len("refs/heads/"):])
			log.Printf("Default branch updated to %s", ref.Name()[len("refs/heads/"):])
			repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", ref.Name()))
			danglingHead = false
			return storer.ErrStop
		}
		for _, ref_n := range refs {
			if !strings.HasPrefix(ref_n, "refs/heads/") {
		for _, refName := range refs {
			if !strings.HasPrefix(refName, "refs/heads/") {
				continue
			}

			ref, _ := repo.Reference(plumbing.ReferenceName(ref_n), false)
			ref, _ := repo.Reference(plumbing.ReferenceName(refName), false)
			if cbk(ref) != nil {
				break
			}
diff --git a/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
index fd0c0ab..bdfd3dc 100644
--- a/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
+++ b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
@@ -10,10 +10,10 @@ Create Date: 2020-07-28 12:04:39.751225
revision = '3c1285bb23e2'
down_revision = '163fc2d2a2ea'

import subprocess
from alembic import op
from sqlalchemy.orm import sessionmaker
from gitsrht.types import Repository
from pygit2 import Repository as GitRepository
try:
    from tqdm import tqdm
except ImportError:
@@ -28,16 +28,15 @@ def upgrade():
    session = Session(bind=bind)
    print("Setting receive.denyDeleteCurrent=ignore")
    for repo in tqdm(session.query(Repository).all()):
        git_repo = GitRepository(repo.path)
        git_repo.config.set_multivar('receive.denyDeleteCurrent', '', 'ignore')
        subprocess.run(["git", "config", "receive.denyDeleteCurrent", "ignore"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def downgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    for repo in tqdm(session.query(Repository).all()):
        git_repo = GitRepository(repo.path)
        try:
            del git_repo.config['receive.denyDeleteCurrent']
        except KeyError:
            pass
        subprocess.run(["git", "config", "--unset", "receive.denyDeleteCurrent"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
index ff84bfa..d19c985 100644
--- a/gitsrht/blueprints/manage.py
+++ b/gitsrht/blueprints/manage.py
@@ -2,20 +2,27 @@ import pygit2
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
from srht.database import db
from srht.oauth import loginrequired
from srht.validation import Validation
from scmsrht.access import check_access, UserAccess
from scmsrht.repos.redirect import BaseRedirectMixin
from scmsrht.repos.repository import RepoVisibility
from scmsrht.webhooks import UserWebhook

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/info/default_branch", methods=["POST"])
@manage.route("/<owner_name>/<repo_name>/settings/info_git", methods=["POST"])
@loginrequired
def settings_info_default_branch_POST(owner_name, repo_name):
def settings_info_git_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    desc = valid.optional("description", default=repo.description)
    visibility = valid.optional("visibility",
            cls=RepoVisibility,
            default=repo.visibility)
    branch = valid.require("default_branch_name", friendly_name="Default branch")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = None
@@ -29,7 +36,13 @@ def settings_info_default_branch_POST(owner_name, repo_name):
        if not valid.ok:
            return render_template("settings_info.html",
                    owner=owner, repo=repo, **valid.kwargs)

        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        repo.visibility = visibility
        repo.description = desc
        UserWebhook.deliver(UserWebhook.Events.repo_update,
                repo.to_dict(), UserWebhook.Subscription.user_id == repo.owner_id)
        db.session.commit()
        return redirect(url_for("manage.settings_info",
            owner_name=owner_name, repo_name=repo_name))
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 9dc53fe..248e1a1 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -387,7 +387,7 @@ def log_rss(owner, repo, ref):
        if not isinstance(commit, pygit2.Commit):
            abort(404)
        commits = get_log(git_repo, commit)
        default_branch = git_repo.default_branch().name[len("refs/heads/"):]
        default_branch = git_repo.default_branch_name()

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
diff --git a/gitsrht/git.py b/gitsrht/git.py
index a197bac..0d10918 100644
--- a/gitsrht/git.py
+++ b/gitsrht/git.py
@@ -55,6 +55,13 @@ class Repository(GitRepository):
        head_ref = self.lookup_reference("HEAD")
        return self.branches.get(head_ref.target[len("refs/heads/"):])

    def default_branch_name(self):
        branch = self.default_branch()
        if branch:
            return branch.name[len("refs/heads/"):]
        else:
            return None

    @property
    def is_empty(self):
        return len(list(self.branches.local)) == 0
diff --git a/gitsrht/templates/settings_info.html b/gitsrht/templates/settings_info.html
index 20466f4..de2dee1 100644
--- a/gitsrht/templates/settings_info.html
+++ b/gitsrht/templates/settings_info.html
@@ -1,48 +1,29 @@
{% set info_action=url_for("manage_git.settings_info_git_POST",
                    owner_name=owner.canonical_name, repo_name=repo.name) %}
{% extends "bases/scmsettings_info.html" %}

{% block content %}
{{ super() }}

<div class="row">
  <div class="col-md-8">
    <form
      method="POST"
      action="{{url_for("manage_git.settings_info_default_branch_POST",
                  owner_name=owner.canonical_name, repo_name=repo.name)}}"
    >
      {{csrf_token()}}
      <div class="form-group">
        <label for="default_branch_name">
          Default branch
        </label>
        <select
          class="form-control {{valid.cls('default_branch_name')}}"
          id="default_branch_name"
          name="default_branch_name"
          {% if repo.git_repo.is_empty %}disabled{% endif %}
        >
          {% set default_branch_name = "" if repo.git_repo.is_empty else
                 repo.git_repo.default_branch().name[len("refs/heads/"):] %}
          {% for branch in repo.git_repo.branches %}
            <option
              value="{{branch}}"
              {% if branch == default_branch_name %}
                selected
              {% endif %}>{{branch}}</option>
          {% else %}
            <option>No branches</option>
          {% endfor %}
        </select>
        {{valid.summary('default_branch_name')}}
      </div>
      <button
        type="submit"
        class="btn btn-primary pull-right"
        {% if repo.git_repo.is_empty %}disabled{% endif %}
      >
        Set {{icon("caret-right")}}
      </button>
    </form>
  </div>
{% block extrafields %}
<div class="form-group">
  <label for="default_branch_name">
    Default branch
  </label>
  <select
    class="form-control {{valid.cls('default_branch_name')}}"
    id="default_branch_name"
    name="default_branch_name"
    {% if repo.git_repo.is_empty %}disabled{% endif %}
  >
    {% set default_branch_name = repo.git_repo.default_branch_name() or "" %}
    {% for branch in repo.git_repo.branches %}
      <option
        value="{{branch}}"
        {% if branch == default_branch_name %}
          selected
        {% endif %}>{{branch}}</option>
    {% else %}
      <option>No branches</option>
    {% endfor %}
  </select>
  {{valid.summary('default_branch_name')}}
</div>
{% endblock %}
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index 9449215..e807750 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -1,5 +1,5 @@
{% macro breadcrumb(ref, repo, path, path_join) %}
{% if ref != repo.git_repo.default_branch().name[("refs/heads/"|length):] %}
{% if ref != repo.git_repo.default_branch_name() %}
<span style="margin-right: 1rem">
  <span class="text-muted">ref:</span> {{ ref }}
</span>
diff --git a/gitsrht/urls.py b/gitsrht/urls.py
index 0061750..c94b96b 100644
--- a/gitsrht/urls.py
+++ b/gitsrht/urls.py
@@ -1,6 +1,5 @@
from flask import url_for
from srht.config import cfg, get_origin
from gitsrht.git import Repository as GitRepository

def clone_urls(repo):
    """Returns the readonly and read/write URL for a given repo."""
@@ -14,8 +13,7 @@ def clone_urls(repo):
    ]

def log_rss_url(repo, ref=None):
    default_branch = repo.git_repo.default_branch().name[len("refs/heads/"):]
    ref = ref if ref != default_branch else None
    ref = ref if ref != repo.git_repo.default_branch_name() else None
    return url_for("repo.log_rss",
        owner=repo.owner.canonical_name,
        repo=repo.name,

scm.sr.ht interdiff against v3:
diff --git a/scmsrht/templates/bases/scmsettings_info.html b/scmsrht/templates/bases/scmsettings_info.html
index 2a2aba5..dced222 100644
--- a/scmsrht/templates/bases/scmsettings_info.html
+++ b/scmsrht/templates/bases/scmsettings_info.html
@@ -3,7 +3,7 @@
{% block content %}
<div class="row">
  <div class="col-md-8">
    <form method="POST">
    <form method="POST" {% if info_action %}action="{{ info_action }}"{% endif %}>
      {{csrf_token()}}
      <div class="form-group">
        <label for="name" style="display: block">
@@ -75,6 +75,7 @@
          </label>
        </div>
      </fieldset>
      {% block extrafields %}{% endblock %}
      <button type="submit" class="btn btn-primary pull-right">
        Save {{icon("caret-right")}}
      </button>
--
2.20.1

[PATCH git.sr.ht v4 01/09] Add Repository.git_repo and GitRepository.default_branch_name(), use them for util.html#breadcrumb()

Details
Message ID
<c5126a3c60dea62d1c59ed45202aeb5c77754380.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +17 -2
---
 gitsrht/git.py               | 7 +++++++
 gitsrht/templates/utils.html | 3 +--
 gitsrht/types/__init__.py    | 9 +++++++++
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/gitsrht/git.py b/gitsrht/git.py
index d87a07f..760a941 100644
--- a/gitsrht/git.py
+++ b/gitsrht/git.py
@@ -60,6 +60,13 @@ class Repository(GitRepository):
            branch = self.branches.get(branch)
        return branch

    def default_branch_name(self):
        branch = self.default_branch()
        if branch:
            return branch.name[len("refs/heads/"):]
        else:
            return None

    @property
    def is_empty(self):
        return len(list(self.branches.local)) == 0
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index dad52be..e807750 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -1,6 +1,5 @@
{% macro breadcrumb(ref, repo, path, path_join) %}
{# TODO: Default branches other than master #}
{% if ref != "master" %}
{% if ref != repo.git_repo.default_branch_name() %}
<span style="margin-right: 1rem">
  <span class="text-muted">ref:</span> {{ ref }}
</span>
diff --git a/gitsrht/types/__init__.py b/gitsrht/types/__init__.py
index 9a526cc..218bcbe 100644
--- a/gitsrht/types/__init__.py
+++ b/gitsrht/types/__init__.py
@@ -3,6 +3,7 @@ import sqlalchemy as sa
from sqlalchemy import event
from srht.database import Base
from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin
from gitsrht.git import Repository as GitRepository
from scmsrht.repos import BaseAccessMixin, BaseRedirectMixin
from scmsrht.repos import BaseRepositoryMixin, RepoVisibility

@@ -19,6 +20,8 @@ class Redirect(Base, BaseRedirectMixin):
    pass

class Repository(Base, BaseRepositoryMixin):
    _git_repo = None

    def update_visibility(self):
        if not os.path.exists(self.path):
            # Repo dir not initialized yet
@@ -34,6 +37,12 @@ class Repository(Base, BaseRepositoryMixin):
        elif not should_exist and os.path.exists(path):
            os.unlink(path)

    @property
    def git_repo(self):
        if not self._git_repo:
            self._git_repo = GitRepository(self.path)
        return self._git_repo

def update_visibility_event(mapper, connection, target):
    target.update_visibility()

--
2.20.1

[PATCH git.sr.ht v4 02/09] Return the right non-'master' default-branch link in RSS feeds

Details
Message ID
<ad603f1b39c825ce436eff02495a56dee8cee548.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -1
---
 gitsrht/blueprints/repo.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 502c821..737e258 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -387,6 +387,7 @@ def log_rss(owner, repo, ref):
        if not isinstance(commit, pygit2.Commit):
            abort(404)
        commits = get_log(git_repo, commit)
        default_branch = git_repo.default_branch_name()

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
@@ -394,7 +395,7 @@ def log_rss(owner, repo, ref):
    link = cfg("git.sr.ht", "origin") + url_for("repo.log",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=ref if ref != "master" else None)
        ref=ref if ref != default_branch else None)

    return generate_feed(repo, commits, title, link, description)

--
2.20.1

[PATCH git.sr.ht v4 03/09] Use the right default branch to log_rss_url()

Details
Message ID
<1ddecb89a968f319473c752366c37acf9821f282.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +1 -1
---
 gitsrht/urls.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gitsrht/urls.py b/gitsrht/urls.py
index b3384e7..c94b96b 100644
--- a/gitsrht/urls.py
+++ b/gitsrht/urls.py
@@ -13,7 +13,7 @@ def clone_urls(repo):
    ]

def log_rss_url(repo, ref=None):
    ref = ref if ref != "master" else None
    ref = ref if ref != repo.git_repo.default_branch_name() else None
    return url_for("repo.log_rss",
        owner=repo.owner.canonical_name,
        repo=repo.name,
--
2.20.1

[PATCH git.sr.ht v4 04/09] Use the default branch for <meta name="go-source"> links

Details
Message ID
<a66f3275aa04eeae00825d6f0b623eab39ed7e31.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +21 -13
---
 gitsrht/blueprints/artifacts.py |  5 +++--
 gitsrht/blueprints/repo.py      | 12 +++++++-----
 gitsrht/blueprints/stats.py     |  3 ++-
 gitsrht/templates/refs.html     |  2 +-
 gitsrht/templates/repo.html     | 12 ++++++++----
 5 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/gitsrht/blueprints/artifacts.py b/gitsrht/blueprints/artifacts.py
index e02d68b..cece6af 100644
--- a/gitsrht/blueprints/artifacts.py
+++ b/gitsrht/blueprints/artifacts.py
@@ -41,15 +41,16 @@ def ref_upload(owner, repo, ref):
        valid = Validation(request)
        f = request.files.get("file")
        valid.expect(f, "File is required", field="file")
        default_branch = git_repo.default_branch()
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        artifact = upload_artifact(valid, repo, target, f, f.filename)
        if not valid.ok:
            return render_template("ref.html", view="refs",
                    owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                    **valid.kwargs)
                    default_branch=default_branch, **valid.kwargs)
        db.session.commit()
        return redirect(url_for("repo.ref",
            owner=owner.canonical_name,
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 737e258..248e1a1 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -417,7 +417,8 @@ def commit(owner, repo, ref):
        return render_template("commit.html", view="log",
            owner=owner, repo=repo, ref=ref, refs=refs,
            commit=commit, parent=parent,
            diff=diff, diffstat=diffstat, pygit2=pygit2)
            diff=diff, diffstat=diffstat, pygit2=pygit2,
            default_branch=git_repo.default_branch())

@repo.route("/<owner>/<repo>/commit/<path:ref>.patch")
def patch(owner, repo, ref):
@@ -469,9 +470,9 @@ def refs(owner, repo):
                git_repo.branches[branch],
                git_repo.get(git_repo.branches[branch].target)
            ) for branch in git_repo.branches.local]
        default_branch = git_repo.default_branch().name
        default_branch = git_repo.default_branch()
        branches = sorted(branches,
                key=lambda b: (b[1].name == default_branch, b[2].commit_time),
                key=lambda b: (b[1].name == default_branch.name, b[2].commit_time),
                reverse=True)

        results_per_page = 10
@@ -493,7 +494,8 @@ def refs(owner, repo):
        return render_template("refs.html", view="refs",
                owner=owner, repo=repo, tags=tags, branches=branches,
                git_repo=git_repo, isinstance=isinstance, pygit2=pygit2,
                page=page + 1, total_pages=total_pages)
                page=page + 1, total_pages=total_pages,
                default_branch=default_branch)


@repo.route("/<owner>/<repo>/refs/rss.xml")
@@ -540,4 +542,4 @@ def ref(owner, repo, ref):
                .filter(Artifact.commit == tag.target.hex)).all()
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag,
                artifacts=artifacts)
                artifacts=artifacts, default_branch=git_repo.default_branch())
diff --git a/gitsrht/blueprints/stats.py b/gitsrht/blueprints/stats.py
index 90bef8f..e883048 100644
--- a/gitsrht/blueprints/stats.py
+++ b/gitsrht/blueprints/stats.py
@@ -38,4 +38,5 @@ def contributors(owner, repo):
        chart_data = get_contrib_chart_data(contributions)

    return render_template("contributors.html", view="contributors",
        owner=owner, repo=repo, chart_data=chart_data)
        owner=owner, repo=repo, chart_data=chart_data,
        default_branch=default_branch)
diff --git a/gitsrht/templates/refs.html b/gitsrht/templates/refs.html
index 8c80d95..dccdea3 100644
--- a/gitsrht/templates/refs.html
+++ b/gitsrht/templates/refs.html
@@ -95,7 +95,7 @@
                  owner=repo.owner.canonical_name,
                  repo=repo.name, ref=name)}}"
                class="btn btn-block {{ "btn-primary"
                  if branch.name == git_repo.default_branch().name
                  if branch.name == default_branch.name
                  else "btn-default" }}"
              >browse {{icon("caret-right")}}</a>
            </div>
diff --git a/gitsrht/templates/repo.html b/gitsrht/templates/repo.html
index 7f662b7..c99ee11 100644
--- a/gitsrht/templates/repo.html
+++ b/gitsrht/templates/repo.html
@@ -6,10 +6,14 @@
{# Man, this is lame #}
<meta name="go-import"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} git {{(repo | clone_urls)[0]}}">
<meta name="go-source"
  content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}
           {{(repo | clone_urls)[0]}}/tree/master{/dir}/{file}#L{line}">
{% if default_branch %}
  <meta name="go-source"
    content="{{domain}}/{{owner.canonical_name}}/{{repo.name}} {{(repo | clone_urls)[0]}}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}
             {{(repo | clone_urls)[0]}}/tree/
               {{- default_branch.name[("refs/heads/"|length):]}}{/dir}/{file}#L{line}">
{% endif %}
{% endblock %}
{% block body %}
<div class="header-tabbed">
--
2.20.1

[PATCH git.sr.ht v4 05/09] Add default-branch-updating logic to post-update hook

Details
Message ID
<a2edce8a0e368b3c14c14b72ada0804242625060.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +46 -0
If the default branch doesn't exist, it will now try to pick another
branch from this push; if none exist, it will set it to the first branch
in iteration order

This, for the very most part, DWIMs, is a no-op for repos with "master"
branches, and a one-time first-push update for repos that use a
different default name

Similarly, "git push origin :trunk trunk:new-trunk" should behave
as expected, changing the default branch from trunk to new-trunk
---
gitsrht-update-hook/post-update.go | 46 ++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)

diff --git a/gitsrht-update-hook/post-update.go b/gitsrht-update-hook/post-update.go
index 3a0d1b8..7e8989f 100644
--- a/gitsrht-update-hook/post-update.go
+++ b/gitsrht-update-hook/post-update.go
@@ -14,6 +14,7 @@ import (
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

func printAutocreateInfo(context PushContext) {
@@ -265,6 +266,51 @@ func postUpdate() {
		}
	}

	// Check if HEAD's dangling (i.e. the default branch doesn't exist)
	// if so, try to find a branch from this push to set as the default
	// if none were found, set the first branch in iteration order as default
	head, err := repo.Reference("HEAD", false)
	if err != nil {
		logger.Fatalf("repo.Reference(\"HEAD\"): %v", err)
	}

	danglingHead := false
	if _, err = repo.Reference(head.Target(), false); err != nil {
		danglingHead = true
	}

	if danglingHead {
		logger.Printf("HEAD dangling at %s", head.Target())

		cbk := func(ref *plumbing.Reference) error {
			if ref == nil {
				return nil
			}

			logger.Printf("Setting HEAD to %s", ref.Name())
			log.Printf("Default branch updated to %s", ref.Name()[len("refs/heads/"):])
			repo.Storer.SetReference(plumbing.NewSymbolicReference("HEAD", ref.Name()))
			danglingHead = false
			return storer.ErrStop
		}
		for _, refName := range refs {
			if !strings.HasPrefix(refName, "refs/heads/") {
				continue
			}

			ref, _ := repo.Reference(plumbing.ReferenceName(refName), false)
			if cbk(ref) != nil {
				break
			}
		}

		if danglingHead {
			if branches, _ := repo.Branches(); branches != nil {
				branches.ForEach(cbk)
			}
		}
	}

	payloadBytes, err := json.Marshal(&payload)
	if err != nil {
		logger.Fatalf("Failed to marshal webhook payload: %v", err)
--
2.20.1

[PATCH git.sr.ht v4 06/09] Make Repository.default_branch() simply read the default branch

Details
Message ID
<9f1c8e73ed44fe78bc776446f11ca31be8d2a769.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -7
Alongside the previous commit this preserves the semantic of
"default_branch() == None" being equivalent to "repository empty",
but that is now managed by the post-update hook
---
 gitsrht/git.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/gitsrht/git.py b/gitsrht/git.py
index 760a941..0d10918 100644
--- a/gitsrht/git.py
+++ b/gitsrht/git.py
@@ -52,13 +52,8 @@ class Repository(GitRepository):
        return super().get(ref)

    def default_branch(self):
        branch = self.branches.get("master")
        if not branch:
            if not any(self.branches.local):
                return None
            branch = list(self.branches.local)[0]
            branch = self.branches.get(branch)
        return branch
        head_ref = self.lookup_reference("HEAD")
        return self.branches.get(head_ref.target[len("refs/heads/"):])

    def default_branch_name(self):
        branch = self.default_branch()
--
2.20.1

[PATCH git.sr.ht v4 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<b09109886f2a52b0bfec876bcf041f4ad67f8c09.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +79 -0
---
 gitsrht/app.py                       |  2 ++
 gitsrht/blueprints/manage.py         | 48 ++++++++++++++++++++++++++++
 gitsrht/templates/settings_info.html | 29 +++++++++++++++++
 3 files changed, 79 insertions(+)
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_info.html

diff --git a/gitsrht/app.py b/gitsrht/app.py
index 525e047..afafdc3 100644
--- a/gitsrht/app.py
+++ b/gitsrht/app.py
@@ -25,6 +25,7 @@ class GitApp(ScmSrhtFlask):
        from gitsrht.blueprints.api import plumbing, porcelain
        from gitsrht.blueprints.artifacts import artifacts
        from gitsrht.blueprints.email import mail
        from gitsrht.blueprints.manage import manage
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats
        from srht.graphql import gql_blueprint
@@ -32,6 +33,7 @@ class GitApp(ScmSrhtFlask):
        self.register_blueprint(plumbing)
        self.register_blueprint(porcelain)
        self.register_blueprint(mail)
        self.register_blueprint(manage)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(webhooks_notify)
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
new file mode 100644
index 0000000..d19c985
--- /dev/null
+++ b/gitsrht/blueprints/manage.py
@@ -0,0 +1,48 @@
import pygit2
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
from srht.database import db
from srht.oauth import loginrequired
from srht.validation import Validation
from scmsrht.access import check_access, UserAccess
from scmsrht.repos.redirect import BaseRedirectMixin
from scmsrht.repos.repository import RepoVisibility
from scmsrht.webhooks import UserWebhook

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/info_git", methods=["POST"])
@loginrequired
def settings_info_git_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    desc = valid.optional("description", default=repo.description)
    visibility = valid.optional("visibility",
            cls=RepoVisibility,
            default=repo.visibility)
    branch = valid.require("default_branch_name", friendly_name="Default branch")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = None
        if branch:
            try:
                new_default_branch = git_repo.branches.get(branch)
            except pygit2.InvalidSpecError:
                pass
        if valid.ok and new_default_branch is None:
            valid.error(f"Branch {branch} not found", field="default_branch_name")
        if not valid.ok:
            return render_template("settings_info.html",
                    owner=owner, repo=repo, **valid.kwargs)

        head_ref = git_repo.lookup_reference("HEAD")
        head_ref.set_target(new_default_branch.name)
        repo.visibility = visibility
        repo.description = desc
        UserWebhook.deliver(UserWebhook.Events.repo_update,
                repo.to_dict(), UserWebhook.Subscription.user_id == repo.owner_id)
        db.session.commit()
        return redirect(url_for("manage.settings_info",
            owner_name=owner_name, repo_name=repo_name))
diff --git a/gitsrht/templates/settings_info.html b/gitsrht/templates/settings_info.html
new file mode 100644
index 0000000..de2dee1
--- /dev/null
+++ b/gitsrht/templates/settings_info.html
@@ -0,0 +1,29 @@
{% set info_action=url_for("manage_git.settings_info_git_POST",
                    owner_name=owner.canonical_name, repo_name=repo.name) %}
{% extends "bases/scmsettings_info.html" %}

{% block extrafields %}
<div class="form-group">
  <label for="default_branch_name">
    Default branch
  </label>
  <select
    class="form-control {{valid.cls('default_branch_name')}}"
    id="default_branch_name"
    name="default_branch_name"
    {% if repo.git_repo.is_empty %}disabled{% endif %}
  >
    {% set default_branch_name = repo.git_repo.default_branch_name() or "" %}
    {% for branch in repo.git_repo.branches %}
      <option
        value="{{branch}}"
        {% if branch == default_branch_name %}
          selected
        {% endif %}>{{branch}}</option>
    {% else %}
      <option>No branches</option>
    {% endfor %}
  </select>
  {{valid.summary('default_branch_name')}}
</div>
{% endblock %}
--
2.20.1

[PATCH git.sr.ht v4 08/09] Set receive.denyDeleteCurrent=ignore for old and newly created repositories

Details
Message ID
<8da45dd116ae19d17099a34d8658ab2faaaf0a07.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +52 -0
By default, git disallows removing the current default branch, with a
large error notice like this:
-- >8 --
remote: error: By default, deleting the current branch is denied, because the next
remote: 'git clone' won't result in any file checked out, causing confusion.
remote:
remote: You can set 'receive.denyDeleteCurrent' configuration variable to
remote: 'warn' or 'ignore' in the remote repository to allow deleting the
remote: current branch, with or without a warning message.
remote:
remote: To squelch this message, you can set it to 'refuse'.
-- >8 --

However, we handle this ourselves in the post-update hook, and will only
let the default branch dangle if there are no branches (the repository
is empty), which is equivalent to the "fresh repository" state.

This also means that *all current repositories need to be migrated*
to the new config to be able to take advantage of the new semantics.
---
 gitsrht-shell/main.go                         |  5 +++
 ...285bb23e2_allow_deleting_default_branch.py | 42 +++++++++++++++++++
 gitsrht/repos.py                              |  5 +++
 3 files changed, 52 insertions(+)
 create mode 100644 gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py

diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index 967b9c1..c29dc89 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,6 +299,11 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {

diff --git a/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
new file mode 100644
index 0000000..bdfd3dc
--- /dev/null
+++ b/gitsrht/alembic/versions/3c1285bb23e2_allow_deleting_default_branch.py
@@ -0,0 +1,42 @@
"""Allow deleting default branch

Revision ID: 3c1285bb23e2
Revises: 163fc2d2a2ea
Create Date: 2020-07-28 12:04:39.751225

"""

# revision identifiers, used by Alembic.
revision = '3c1285bb23e2'
down_revision = '163fc2d2a2ea'

import subprocess
from alembic import op
from sqlalchemy.orm import sessionmaker
from gitsrht.types import Repository
try:
    from tqdm import tqdm
except ImportError:
    def tqdm(iterable):
        yield from iterable

Session = sessionmaker()


def upgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    print("Setting receive.denyDeleteCurrent=ignore")
    for repo in tqdm(session.query(Repository).all()):
        subprocess.run(["git", "config", "receive.denyDeleteCurrent", "ignore"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def downgrade():
    bind = op.get_bind()
    session = Session(bind=bind)
    for repo in tqdm(session.query(Repository).all()):
        subprocess.run(["git", "config", "--unset", "receive.denyDeleteCurrent"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
diff --git a/gitsrht/repos.py b/gitsrht/repos.py
index 1bf7b40..2ab6e46 100644
--- a/gitsrht/repos.py
+++ b/gitsrht/repos.py
@@ -90,6 +90,11 @@ class GitRepoApi(SimpleRepoApi):
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["git", "config", "srht.repo-id", str(repo.id)], check=True,
            cwd=repo.path, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        # We handle this ourselves in the post-update hook, and git's
        # default behaviour is to print a large notice and reject the push entirely
        subprocess.run(["git", "config", "receive.denyDeleteCurrent", "ignore"],
            check=True, cwd=repo.path,
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.run(["ln", "-s",
                post_update,
                os.path.join(repo.path, "hooks", "pre-receive")
--
2.20.1

[PATCH git.sr.ht v4 09/09] Also set srht.repo-id in gitsrht-shell for autocreated repositories

Details
Message ID
<8749bb2f9a213861bce1e829b491bda8e3c1ce52.1596123756.git.nabijaczleweli@nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +6 -1
This mirrors gitsrht/repos.py, as it should have all along
---
 gitsrht-shell/main.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/gitsrht-shell/main.go b/gitsrht-shell/main.go
index c29dc89..449c959 100644
--- a/gitsrht-shell/main.go
+++ b/gitsrht-shell/main.go
@@ -299,10 +299,15 @@ func main() {

					notFound("git init", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"srht.repo-id", strconv.Itoa(repoId)).Run(); err != nil {

					notFound("git config srht.repo-id", err)
				}
				if err = exec.Command("git", "-C", path, "config",
					"receive.denyDeleteCurrent", "ignore").Run(); err != nil {

					notFound("git config", err)
					notFound("git config receive.denyDeleteCurrent", err)
				}
				if err = exec.Command("ln", "-s", postUpdate,
					gopath.Join(path, "hooks", "update")).Run(); err != nil {
--
2.20.1

[PATCH scm.sr.ht v4 01/01] Allow extending /~n/r/settings/info in SCMs

Details
Message ID
<20200730160104.fenumqgtkkatydd2@tarta.local.nabijaczleweli.xyz>
In-Reply-To
<cover.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +86 -84
---
 scmsrht/templates/bases/scmsettings_info.html | 85 +++++++++++++++++++
 scmsrht/templates/settings_info.html          | 85 +------------------
 2 files changed, 86 insertions(+), 84 deletions(-)
 create mode 100644 scmsrht/templates/bases/scmsettings_info.html

diff --git a/scmsrht/templates/bases/scmsettings_info.html b/scmsrht/templates/bases/scmsettings_info.html
new file mode 100644
index 0000000..dced222
--- /dev/null
+++ b/scmsrht/templates/bases/scmsettings_info.html
@@ -0,0 +1,85 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-8">
    <form method="POST" {% if info_action %}action="{{ info_action }}"{% endif %}>
      {{csrf_token()}}
      <div class="form-group">
        <label for="name" style="display: block">
          Repository name
          <a
            href="/{{ owner.canonical_name }}/{{ repo.name }}/settings/rename"
            class="pull-right"
          >Rename?</a>
        </label>
        <input
          type="text"
          class="form-control"
          id="name"
          value="{{repo.name}}"
          readonly />
      </div>
      <div class="form-group">
        <label for="description">
          Description
        </label>
        <input
          type="text"
          class="form-control"
          id="description"
          name="description"
          value="{{repo.description}}" />
      </div>
      <fieldset class="form-group">
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Publically visible and listed on your profile"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="public"
              {{ "checked" if repo.visibility.value == "public" else "" }}
            > Public
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
              class="form-check-label"
              title="Visible to anyone with the link, but not shown on your profile"
            >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="unlisted"
              {{ "checked" if repo.visibility.value == "unlisted" else "" }}
            > Unlisted
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Only visible to you and your collaborators"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="private"
              {{ "checked" if repo.visibility.value == "private" else "" }}
            > Private
          </label>
        </div>
      </fieldset>
      {% block extrafields %}{% endblock %}
      <button type="submit" class="btn btn-primary pull-right">
        Save {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
diff --git a/scmsrht/templates/settings_info.html b/scmsrht/templates/settings_info.html
index 2a2aba5..da61777 100644
--- a/scmsrht/templates/settings_info.html
+++ b/scmsrht/templates/settings_info.html
@@ -1,84 +1 @@
{% extends "settings.html" %}

{% block content %}
<div class="row">
  <div class="col-md-8">
    <form method="POST">
      {{csrf_token()}}
      <div class="form-group">
        <label for="name" style="display: block">
          Repository name
          <a
            href="/{{ owner.canonical_name }}/{{ repo.name }}/settings/rename"
            class="pull-right"
          >Rename?</a>
        </label>
        <input
          type="text"
          class="form-control"
          id="name"
          value="{{repo.name}}"
          readonly />
      </div>
      <div class="form-group">
        <label for="description">
          Description
        </label>
        <input
          type="text"
          class="form-control"
          id="description"
          name="description"
          value="{{repo.description}}" />
      </div>
      <fieldset class="form-group">
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Publically visible and listed on your profile"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="public"
              {{ "checked" if repo.visibility.value == "public" else "" }}
            > Public
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
              class="form-check-label"
              title="Visible to anyone with the link, but not shown on your profile"
            >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="unlisted"
              {{ "checked" if repo.visibility.value == "unlisted" else "" }}
            > Unlisted
          </label>
        </div>
        <div class="form-check form-check-inline">
          <label
            class="form-check-label"
            title="Only visible to you and your collaborators"
          >
            <input
              class="form-check-input"
              type="radio"
              name="visibility"
              value="private"
              {{ "checked" if repo.visibility.value == "private" else "" }}
            > Private
          </label>
        </div>
      </fieldset>
      <button type="submit" class="btn btn-primary pull-right">
        Save {{icon("caret-right")}}
      </button>
    </form>
  </div>
</div>
{% endblock %}
{% extends "bases/scmsettings_info.html" %}
--
2.20.1

[PATCH git.sr.ht v4.1 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<20200730163849.aaucdaehh2guucf3@tarta.local.nabijaczleweli.xyz>
In-Reply-To
<b09109886f2a52b0bfec876bcf041f4ad67f8c09.1596123756.git.nabijaczleweli@nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Patch: +78 -0
---
Minor fuckup, the v4 version treated the default branch as required and
hence failed if the repo had no branches and I forgot to test that case,
this fixes that.

Hoping to avoid a full resend because it's a right pain in the arse to
make and send those 11 hand-crafted e-mails.

The new pull branch is no-gods-no-masters-v4.1 with
ca6318a584a20b1b3db8fcfb9be286a6c9b2553d.

Sorry about this.

 gitsrht/app.py                       |  2 ++
 gitsrht/blueprints/manage.py         | 47 ++++++++++++++++++++++++++++
 gitsrht/templates/settings_info.html | 29 +++++++++++++++++
 3 files changed, 78 insertions(+)
 create mode 100644 gitsrht/blueprints/manage.py
 create mode 100644 gitsrht/templates/settings_info.html

diff --git a/gitsrht/app.py b/gitsrht/app.py
index 525e047..afafdc3 100644
--- a/gitsrht/app.py
+++ b/gitsrht/app.py
@@ -25,6 +25,7 @@ class GitApp(ScmSrhtFlask):
        from gitsrht.blueprints.api import plumbing, porcelain
        from gitsrht.blueprints.artifacts import artifacts
        from gitsrht.blueprints.email import mail
        from gitsrht.blueprints.manage import manage
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats
        from srht.graphql import gql_blueprint
@@ -32,6 +33,7 @@ class GitApp(ScmSrhtFlask):
        self.register_blueprint(plumbing)
        self.register_blueprint(porcelain)
        self.register_blueprint(mail)
        self.register_blueprint(manage)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(webhooks_notify)
diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py
new file mode 100644
index 0000000..f51cc99
--- /dev/null
+++ b/gitsrht/blueprints/manage.py
@@ -0,0 +1,47 @@
import pygit2
from flask import Blueprint, request, render_template
from flask import redirect, url_for
from gitsrht.git import Repository as GitRepository
from srht.database import db
from srht.oauth import loginrequired
from srht.validation import Validation
from scmsrht.access import check_access, UserAccess
from scmsrht.repos.redirect import BaseRedirectMixin
from scmsrht.repos.repository import RepoVisibility
from scmsrht.webhooks import UserWebhook

manage = Blueprint('manage_git', __name__)

@manage.route("/<owner_name>/<repo_name>/settings/info_git", methods=["POST"])
@loginrequired
def settings_info_git_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    desc = valid.optional("description", default=repo.description)
    visibility = valid.optional("visibility",
            cls=RepoVisibility,
            default=repo.visibility)
    branch = valid.optional("default_branch_name")
    with GitRepository(repo.path) as git_repo:
        new_default_branch = None
        if branch:
            try:
                new_default_branch = git_repo.branches.get(branch)
            except pygit2.InvalidSpecError:
                valid.error(f"Branch {branch} not found", field="default_branch_name")
        if not valid.ok:
            return render_template("settings_info.html",
                    owner=owner, repo=repo, **valid.kwargs)
        if new_default_branch:
            head_ref = git_repo.lookup_reference("HEAD")
            head_ref.set_target(new_default_branch.name)

        repo.visibility = visibility
        repo.description = desc
        UserWebhook.deliver(UserWebhook.Events.repo_update,
                repo.to_dict(), UserWebhook.Subscription.user_id == repo.owner_id)
        db.session.commit()
        return redirect(url_for("manage.settings_info",
            owner_name=owner_name, repo_name=repo_name))
diff --git a/gitsrht/templates/settings_info.html b/gitsrht/templates/settings_info.html
new file mode 100644
index 0000000..de2dee1
--- /dev/null
+++ b/gitsrht/templates/settings_info.html
@@ -0,0 +1,29 @@
{% set info_action=url_for("manage_git.settings_info_git_POST",
                    owner_name=owner.canonical_name, repo_name=repo.name) %}
{% extends "bases/scmsettings_info.html" %}

{% block extrafields %}
<div class="form-group">
  <label for="default_branch_name">
    Default branch
  </label>
  <select
    class="form-control {{valid.cls('default_branch_name')}}"
    id="default_branch_name"
    name="default_branch_name"
    {% if repo.git_repo.is_empty %}disabled{% endif %}
  >
    {% set default_branch_name = repo.git_repo.default_branch_name() or "" %}
    {% for branch in repo.git_repo.branches %}
      <option
        value="{{branch}}"
        {% if branch == default_branch_name %}
          selected
        {% endif %}>{{branch}}</option>
    {% else %}
      <option>No branches</option>
    {% endfor %}
  </select>
  {{valid.summary('default_branch_name')}}
</div>
{% endblock %}
-- 
2.20.1

Re: [PATCH git.sr.ht v4.1 07/09] Allow setting the default branch from the info settings tab

Details
Message ID
<C4P4GR2A14EL.1F0I4XCECQV26@homura>
In-Reply-To
<20200730163849.aaucdaehh2guucf3@tarta.local.nabijaczleweli.xyz> (view parent)
DKIM signature
pass
Download raw message
Thanks!

To git@git.sr.ht:~sircmpwn/git.sr.ht
   3ae9618..8f88443  master -> master