From: Nikolay Shaplov Date: Sun, 7 Jun 2026 03:45:07 +0000 (+0000) Subject: Initial commit X-Git-Url: http://gitweb.nataraj.world/?a=commitdiff_plain;h=5d792b6baf662d9db6017801b6d2b0fc766c5c82;p=git4ai.git Initial commit --- 5d792b6baf662d9db6017801b6d2b0fc766c5c82 diff --git a/cgi/git4ai-cat-file.pl b/cgi/git4ai-cat-file.pl new file mode 100755 index 0000000..6781098 --- /dev/null +++ b/cgi/git4ai-cat-file.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl +use strict; +use warnings; + +my $GIT_ROOT = '/var/lib/git'; + +my $repo = $ENV{GIT_REPO}; +my $blob_id = $ENV{GIT_ID} || $ENV{GIT_BLOB_ID}; + +$repo =~ s{[^a-zA-Z0-9._-]}{}g; +$blob_id =~ s{[^0-9a-f]}{}g; + +die unless $repo; +die unless $blob_id; + +open(my $fh, '-|', 'git', "--git-dir=$GIT_ROOT/$repo", + 'cat-file', 'blob', $blob_id) + or die "git cat-file failed: $!"; + +# Читаем первые 8000 байт +my $buf = ''; +read($fh, $buf, 8000); + +# Определяем тип по наличию нулевых байт. Так делает сам git +my $is_binary = index($buf, "\x00") >= 0; +my $ct = $is_binary + ? 'application/octet-stream' + : 'text/plain; charset=utf-8'; + +binmode(STDOUT, ':raw'); +print "Content-Type: $ct\r\n"; +print "Cache-Control: no-store, no-cache, must-revalidate\r\n"; +print "\r\n"; + +# Выводим буфер и остаток +print $buf; +while (read($fh, my $chunk, 65536)) { + print $chunk; +} + +close($fh); diff --git a/cgi/git4ai-log.pl b/cgi/git4ai-log.pl new file mode 100755 index 0000000..3564972 --- /dev/null +++ b/cgi/git4ai-log.pl @@ -0,0 +1,88 @@ +#!/usr/bin/perl +use strict; +use warnings; + +my $GIT_ROOT = '/var/lib/git'; +my $PER_PAGE = 20; +my $BASE_URL = 'https://gitweb.nataraj.world'; + +my $repo = $ENV{GIT_REPO}; +my $branch = $ENV{GIT_BRANCH} // 'master'; +my $offset = int($ENV{GIT_OFFSET} // 0); +my $nonce = $ENV{GIT_NONCE} || $ENV{REQUEST_ID}; + + +$repo =~ s{[^a-zA-Z0-9._-]}{}g; +$branch =~ s{[^a-zA-Z0-9._/\-]}{}g; +$offset = 0 if $offset < 0; +$nonce =~ s{[^a-zA-Z0-9._-]}{}g; + +die unless $repo; +die unless $branch; + +my $rand = int(rand(1_000_000_000)); +my $git_dir = "$GIT_ROOT/$repo"; + +print "Content-Type: text/html; charset=utf-8\r\n"; +print "Cache-Control: no-store, no-cache, must-revalidate\r\n"; +print "\r\n"; + +open(my $fh, '-|', 'git', "--git-dir=$git_dir", 'log', + '--format=%H%x09%s%x09%an%x09%ad', + '--date=short', + "--skip=$offset", + "--max-count=$PER_PAGE", + "refs/heads/$branch") + or die "git failed: $!"; + +my @commits; +while (<$fh>) { + chomp; + my ($hash, $subject, $author, $date) = split /\t/, $_, 4; + push @commits, { + hash => $hash, + subject => $subject, + author => $author, + date => $date, + }; +} +close($fh); + +my $prev = $offset - $PER_PAGE; $prev = 0 if $prev < 0; +my $next = $offset + $PER_PAGE; + +print < + + + + git log: $repo / $branch + + +

git log: $repo / $branch (offset: $offset)

+Nonce: $nonce

+HTML + +print "← новее   " + if $offset > 0; +print "старее →" + if @commits == $PER_PAGE; + +print "\n\n"; +print "\n"; + +for my $c (@commits) { + my $short = substr($c->{hash}, 0, 8); + my $show_url = "$BASE_URL/gitai-show/$repo/$c->{hash}"; + my $tree_url = "$BASE_URL/gitai-ls-tree/$repo/$c->{hash}/0/r$rand"; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; +} + +print "
DiffДеревоДатаАвторСообщение
$shorttree$c->{date}$c->{author}$c->{subject}
\n"; +print "\n"; diff --git a/cgi/git4ai-ls-tree.pl b/cgi/git4ai-ls-tree.pl new file mode 100755 index 0000000..137ed05 --- /dev/null +++ b/cgi/git4ai-ls-tree.pl @@ -0,0 +1,83 @@ +#!/usr/bin/perl +use strict; +use warnings; + +my $GIT_ROOT = '/var/lib/git'; +my $PER_PAGE = 50; +my $BASE_URL = 'https://gitweb.nataraj.world'; + +my $repo = $ENV{GIT_REPO}; +my $commit_id = $ENV{GIT_ID} || $ENV{GIT_COMMIT_ID}; +my $offset = int($ENV{GIT_OFFSET} // 0); + +$repo =~ s{[^a-zA-Z0-9._-]}{}g; +$commit_id =~ s{[^0-9a-f]}{}g; +$offset = 0 if $offset < 0; + +die unless $repo; +die unless $commit_id; + +my $rand = int(rand(1_000_000_000)); +my $git_dir = "$GIT_ROOT/$repo"; + +print "Content-Type: text/html; charset=utf-8\r\n"; +print "Cache-Control: no-store, no-cache, must-revalidate\r\n"; +print "\r\n"; + +# Показываем информацию о коммите +open(my $log, '-|', 'git', "--git-dir=$git_dir", 'log', + '-1', '--format=%H %s (%an, %ad)', '--date=short', $commit_id) + or die "git log failed: $!"; +my $commit_info = <$log> // ''; +chomp $commit_info; +close($log); + +# Получаем все файлы +open(my $fh, '-|', 'git', "--git-dir=$git_dir", 'ls-tree', + '-r', '--long', $commit_id) + or die "git ls-tree failed: $!"; + +my @files; +while (<$fh>) { + chomp; + if (/^(\S+)\s+blob\s+([0-9a-f]{40})\s+(\S+)\t(.+)$/) { + push @files, { hash => $2, size => $3, name => $4 }; + } +} +close($fh); + +my $total = scalar @files; +my @page = grep { defined } @files[$offset .. $offset + $PER_PAGE - 1]; + +my $prev = $offset - $PER_PAGE; $prev = 0 if $prev < 0; +my $next = $offset + $PER_PAGE; + +print < + + +git ls-tree: $repo / $commit_id + + +

git ls-tree: $repo / $commit_id

+

$commit_info

+

Файлов: $total, показаны $offset — @{[$offset + scalar @page]}

+HTML + +print "← выше   " + if $offset > 0; +print "ниже →" + if $next < $total; + +print "\n\n"; +print "\n"; + +for my $f (@page) { + my $raw_url = "$BASE_URL/gitai-cat-file/$repo/$f->{hash}/r$rand"; + my $short = substr($f->{hash}, 0, 8); + printf "\n", + $raw_url, $short, $f->{size}, $f->{name}; +} + +print "
БлобРазмерФайл
%s%s%s
\n"; +print "\n"; diff --git a/cgi/git4ai-show.pl b/cgi/git4ai-show.pl new file mode 100755 index 0000000..0f9eb91 --- /dev/null +++ b/cgi/git4ai-show.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl +use strict; +use warnings; + +my $GIT_ROOT = '/var/lib/git'; + +my $repo = $ENV{GIT_REPO}; +my $commit = $ENV{GIT_ID} || $ENV{GIT_COMMIT}; + +$repo =~ s{[^a-zA-Z0-9._-]}{}g; +$commit =~ s{[^0-9a-f]}{}g; + +die unless $repo; +die unless $commit; + +print "Content-Type: text/plain; charset=utf-8\r\n"; +print "Cache-Control: no-store, no-cache, must-revalidate\r\n"; +print "\r\n"; + +exec('git', "--git-dir=$GIT_ROOT/$repo", 'show', $commit); diff --git a/cgi/git4ai.pl b/cgi/git4ai.pl new file mode 100755 index 0000000..2272d48 --- /dev/null +++ b/cgi/git4ai.pl @@ -0,0 +1,57 @@ +#!/usr/bin/perl +use strict; +use warnings; + +my $BASE_URL = 'https://gitweb.nataraj.world'; +my $LINKS = 50; + +my $repo = $ENV{GIT_REPO}; +my $branch = $ENV{GIT_BRANCH}; + +$repo =~ s{[^a-zA-Z0-9._-]}{}g; +$branch =~ s{[^a-zA-Z0-9._/\-]}{}g; + +die unless $repo; +die unless $branch; + +print "Content-Type: text/html; charset=utf-8\r\n"; +print "Cache-Control: no-store, no-cache, must-revalidate\r\n"; +print "\r\n"; + +print < + + +gitai: $repo / $branch + + + +

Это web entry point для ИИ для git репозитория $repo, ветки $branch.

+ +

Коммит лог для ветки доступен по ссылке вот этой (страница не кешируется)

+ +

Прочитав коммит лог, ты сможешь посмотреть на ls-tree для заданного коммита по ссылке вида https://gitweb.nataraj.world/gitai-ls-tree/$repo/[commit_id]

+ +

Так же посмотреть на блобы упомянуте в ls-tree по ссылке вида https://gitweb.nataraj.world/gitai-raw/$repo/[blob_id]

+ +

А так же на diff самих коммитов по ссылке вида https://gitweb.nataraj.world/gitai-show/[commit_id]

+ +

Полный текст ссылок будет присутвовать на соответсвующих страницах, так что ограничений на fetch у тебя не будет

+ +

Используй это знание для того чтобы получать ифнормацию из репозитория $repo/

+ + +HTML + +=cut +

Список одноразовых ссылок на незакешированный git log для репозитория $repo ветки $branch

+
    +HTML + +for (1..$LINKS) { + my $rand = int(rand(1_000_000_000)); + print "
  1. r$rand
  2. \n"; +} +=cut + +print "
\n"; diff --git a/cgi/gitai-cat-file.pl b/cgi/gitai-cat-file.pl new file mode 120000 index 0000000..7de7d62 --- /dev/null +++ b/cgi/gitai-cat-file.pl @@ -0,0 +1 @@ +git4ai-cat-file.pl \ No newline at end of file diff --git a/cgi/gitai-log.pl b/cgi/gitai-log.pl new file mode 120000 index 0000000..c765b9f --- /dev/null +++ b/cgi/gitai-log.pl @@ -0,0 +1 @@ +git4ai-log.pl \ No newline at end of file diff --git a/cgi/gitai-ls-tree.pl b/cgi/gitai-ls-tree.pl new file mode 120000 index 0000000..9489a5d --- /dev/null +++ b/cgi/gitai-ls-tree.pl @@ -0,0 +1 @@ +git4ai-ls-tree.pl \ No newline at end of file diff --git a/cgi/gitai-show.pl b/cgi/gitai-show.pl new file mode 120000 index 0000000..e049d8b --- /dev/null +++ b/cgi/gitai-show.pl @@ -0,0 +1 @@ +git4ai-show.pl \ No newline at end of file diff --git a/cgi/gitai.pl b/cgi/gitai.pl new file mode 120000 index 0000000..4ab2b52 --- /dev/null +++ b/cgi/gitai.pl @@ -0,0 +1 @@ +git4ai.pl \ No newline at end of file diff --git a/nginx-conf/git4ai b/nginx-conf/git4ai new file mode 100644 index 0000000..f18bfdf --- /dev/null +++ b/nginx-conf/git4ai @@ -0,0 +1,233 @@ +map $uri $git4ai_script { + + ~^/git4ai-show/ /srv/cgi/git4ai-show.pl; + ~^/git4ai-cat-file/ /srv/cgi/git4ai-cat-file.pl; + ~^/git4ai-log/ /srv/cgi/git4ai-log.pl; + ~^/git4ai-ls-tree/ /srv/cgi/git4ai-ls-tree.pl; + ~^/git4ai/ /srv/cgi/git4ai.pl; +} + +server { + listen 80; + + location /index.cgi { + root /usr/share/gitweb/; + include fastcgi_params; + gzip off; + + fastcgi_param LANG "en_US.UTF-8"; + fastcgi_param LC_ALL "en_US.UTF-8"; + fastcgi_param PERL_UNICODE "SD"; # S=stdin/stdout UTF-8, D=@ARGV UTF-8 + + fastcgi_param SCRIPT_NAME $uri; + fastcgi_param GITWEB_CONFIG /etc/gitweb.conf; + fastcgi_pass unix:/var/run/fcgiwrap.socket; + + # Не кешируем. Иначе колд айай начинает видеть старые версии вместо актуальных + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + expires -1; + } + + location / { + root /usr/share/gitweb/; + index index.cgi; + + # Не кешируем. Иначе колд айай начинает видеть старые версии вместо актуальных + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + expires -1; + } + + + location /gitweb3/index.cgi { + root /usr/share/gitweb/; + include fastcgi_params; + gzip off; + + fastcgi_param LANG "en_US.UTF-8"; + fastcgi_param LC_ALL "en_US.UTF-8"; + fastcgi_param PERL_UNICODE "D"; # S=stdin/stdout UTF-8 (убрано), D=@ARGV UTF-8 + + fastcgi_param SCRIPT_NAME $uri; + fastcgi_param SCRIPT_FILENAME /usr/share/gitweb/index.cgi; + + fastcgi_param GITWEB_CONFIG /etc/gitweb.conf; + fastcgi_pass unix:/var/run/fcgiwrap.socket; + + # Не кешируем. Иначе колд айай начинает видеть старые версии вместо актуальных + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + expires -1; + } + + location /gitweb3 { + root /usr/share/gitweb/; + # index index.cgi; + + try_files $uri /gitweb3/index.cgi$is_args$args; + + # Не кешируем. Иначе колд айай начинает видеть старые версии вместо актуальных + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + expires -1; + } + + + +location ~ "^/raw/([^/]+)/([0-9a-f]{4,40})$" { + rewrite "^/raw/([^/]+)/([0-9a-f]{4,40})$" /?p=$1&a=blob_plain&h=$2 break; + proxy_pass http://127.0.0.1:80; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + + +location = /raw/ { + default_type text/plain; + return 200 "https://gitweb.nataraj.world/raw/articles.git/\n"; + add_header Cache-Control "no-store" always; +} + + + # Временная копия на то время пока не протух кеш +location /gitweb2/ { + proxy_pass http://127.0.0.1:80/; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Pragma "no-cache" always; + expires -1; +} + + +# Если без nonce'а добавляем nonce. +location ~ "^/git4ai-log/([^/]+)/([^/]+)/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/git4ai-log/$1/$2/$3/$4/$request_id; + add_header Cache-Control "no-store" always; +} + +location ~ "^/git4ai-log/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/git4ai-log/$1/$2/-/0; + add_header Cache-Control "no-store" always; +} + +location ~ "^/git4ai-log/([^/]+)/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/git4ai-log/$1/$2/-/$3; + add_header Cache-Control "no-store" always; +} + +# repo branch id offset nonce +location ~ "^/git4ai[^/]*/([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+|-)/([0-9a-f]{4,40}|-)/([0-9]+|-)(?:/([a-zA-Z0-9]+))?$" { + set $git_repo $1; + set $git_branch $2; + set $git_id $3; + set $git_offset $4; + set $git_nonce $5; + + # Должно идти после того как все $N переменные использованы. Так как map $git4ai_scrit их портит + fastcgi_param SCRIPT_FILENAME $git4ai_script; + fastcgi_param GIT_REPO $git_repo; + fastcgi_param GIT_BRANCH $git_branch; + fastcgi_param GIT_ID $git_id; + fastcgi_param GIT_OFFSET $git_offset; + fastcgi_param GIT_NONCE $git_nonce; + + fastcgi_pass unix:/var/run/fcgiwrap.socket; + include fastcgi_params; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + +location ~ "^/gitai1/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/gitai/$1/$2/r$request_id; + add_header Cache-Control "no-store" always; +} + +location ~ "^/gitai/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/gitai/$1/$2/r$request_id; + add_header Cache-Control "no-store" always; +} + +location ~ "^/gitai/([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)/[a-zA-Z0-9]+$" { + fastcgi_pass unix:/var/run/fcgiwrap.socket; + fastcgi_param SCRIPT_FILENAME /srv/cgi/gitai.pl; + fastcgi_param GIT_REPO $1; + fastcgi_param GIT_BRANCH $2; + include fastcgi_params; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + +location ~ "^/gitai-log/([^/]+)/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/gitai-log/$1/$2/$3/$request_id; + add_header Cache-Control "no-store" always; +} + +location ~ "^/gitai1-log/([^/]+)/([^/]+)/([^/]+)$" { + return 302 https://gitweb.nataraj.world/gitai-log/$1/$2/$3/$request_id; + add_header Cache-Control "no-store" always; +} + +location ~ "^/gitai-log/([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)/([0-9]+)/[a-zA-Z0-9]+$" { + fastcgi_pass unix:/var/run/fcgiwrap.socket; + fastcgi_param SCRIPT_FILENAME /srv/cgi/gitai-log.pl; + fastcgi_param GIT_REPO $1; + fastcgi_param GIT_BRANCH $2; + fastcgi_param GIT_OFFSET $3; + fastcgi_param REQUEST_ID $request_id; + include fastcgi_params; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + +location ~ "^/gitai-ls-tree/([a-zA-Z0-9._-]+)/([0-9a-f]{4,40})/([0-9]+)(?:/[a-zA-Z0-9]+)?$" { + fastcgi_pass unix:/var/run/fcgiwrap.socket; + fastcgi_param SCRIPT_FILENAME /srv/cgi/gitai-ls-tree.pl; + fastcgi_param GIT_REPO $1; + fastcgi_param GIT_COMMIT_ID $2; + fastcgi_param GIT_OFFSET $3; + include fastcgi_params; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + +location ~ "^/gitai-show/([^/]+)/([0-9a-f]{4,40})(?:/[a-zA-Z0-9]+)?$" { + fastcgi_pass unix:/var/run/fcgiwrap.socket; + fastcgi_param SCRIPT_FILENAME /srv/cgi/gitai-show.pl; + fastcgi_param GIT_REPO $1; + fastcgi_param GIT_COMMIT $2; + include fastcgi_params; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + +location ~ "^/gitai-cat-file/([^/]+)/([0-9a-f]{4,40})(?:/[a-zA-Z0-9]+)?$" { + fastcgi_pass unix:/var/run/fcgiwrap.socket; + fastcgi_param SCRIPT_FILENAME /srv/cgi/gitai-cat-file.pl; + fastcgi_param GIT_REPO $1; + fastcgi_param GIT_BLOB_ID $2; + include fastcgi_params; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; +} + +#location ~ "^/gitai-raw/([^/]+)/([0-9a-f]{4,40})(?:/r[0-9]+)?$" { +# rewrite "^/gitai-raw/([^/]+)/([0-9a-f]{4,40})(?:/r[0-9]+)?$" /?p=$1&a=blob_plain&h=$2 break; +# proxy_pass http://127.0.0.1:80; +# add_header Cache-Control "no-store, no-cache, must-revalidate" always; +# expires -1; +#} + + + location ~ ^/git(/.*) { + fastcgi_pass unix:/var/run/fcgiwrap.socket; + fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; + fastcgi_param GIT_PROJECT_ROOT /var/lib/git; + fastcgi_param GIT_HTTP_EXPORT_ALL ""; + fastcgi_param PATH_INFO $1; + include fastcgi_params; + } + + location /hooks { + proxy_pass http://127.0.0.1:9000/hooks; + } +}