Merge branch 'main' into v2

* main:
  build: use specific token for pushing release tags
  fix(gui): update `uncamel()` to handle strings like 'IDs' (fixes #10128) (#10131)
  refactor: use slices package for sort (#10132)
  build: process for automatic release tags (#10133)
  chore(gui, man, authors): update docs, translations, and contributors
This commit is contained in:
Jakob Borg
2025-05-26 14:22:30 +02:00
51 changed files with 375 additions and 115 deletions
+30 -6
View File
@@ -162,7 +162,7 @@ jobs:
codesign-windows: codesign-windows:
name: Codesign for Windows name: Codesign for Windows
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
environment: release environment: release
runs-on: windows-latest runs-on: windows-latest
needs: needs:
@@ -282,7 +282,7 @@ jobs:
package-macos: package-macos:
name: Package for macOS name: Package for macOS
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
environment: release environment: release
env: env:
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }} CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
@@ -385,7 +385,7 @@ jobs:
notarize-macos: notarize-macos:
name: Notarize for macOS name: Notarize for macOS
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
environment: release environment: release
needs: needs:
- package-macos - package-macos
@@ -529,7 +529,7 @@ jobs:
sign-for-upgrade: sign-for-upgrade:
name: Sign for upgrade name: Sign for upgrade
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
environment: release environment: release
needs: needs:
- codesign-windows - codesign-windows
@@ -732,6 +732,8 @@ jobs:
name: Publish release files name: Publish release files
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v')) if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
environment: release environment: release
permissions:
contents: write
needs: needs:
- sign-for-upgrade - sign-for-upgrade
- package-debian - package-debian
@@ -791,13 +793,35 @@ jobs:
with: with:
args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest
- name: Create GitHub release and push binaries
run: |
maybePrerelease=""
if [[ $VERSION == *-* ]]; then
maybePrerelease="--prerelease"
fi
export GH_PROMPT_DISABLED=1
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
gh release create \
"$VERSION" \
$maybePrerelease \
--title "$VERSION" \
--notes-from-tag
fi
gh release upload "$VERSION" \
packages/*.asc packages/*.json \
packages/syncthing-*.tar.gz \
packages/syncthing-*.zip \
packages/syncthing*.deb
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# #
# Push Debian/APT archive # Push Debian/APT archive
# #
publish-apt: publish-apt:
name: Publish APT name: Publish APT
if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
environment: release environment: release
needs: needs:
- package-debian - package-debian
@@ -878,7 +902,7 @@ jobs:
docker-syncthing: docker-syncthing:
name: Build and push Docker images name: Build and push Docker images
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release-nightly' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/tags/v'))
environment: docker environment: docker
env: env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+57
View File
@@ -0,0 +1,57 @@
name: Release Syncthing
on:
push:
branches:
- release
- release-rc*
permissions:
contents: write
jobs:
create-release-tag:
name: Create release tag
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
token: ${{ secrets.STRELEASE_GITHUB_TOKEN }}
- uses: actions/setup-go@v5
with:
go-version: stable
- name: Get svu
run: |
go install github.com/caarlos0/svu@latest
- name: Determine version to release
run: |
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
next=$(svu next)
else
next=$(svu prerelease --pre-release rc)
fi
echo "NEXT=$next" >> $GITHUB_ENV
echo "Next version is $next"
prev=$(git describe --exclude "*-*" --abbrev=0)
echo "PREV=$prev" >> $GITHUB_ENV
echo "Previous version is $prev"
- name: Determine release notes
run: |
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
env:
GITHUB_TOKEN: ${{ secrets.STRELEASE_GITHUB_TOKEN }}
- name: Create and push tag
run: |
git config --global user.name 'Syncthing Release Automation'
git config --global user.email 'release@syncthing.net'
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
git push origin "$NEXT"
+4 -3
View File
@@ -8,6 +8,7 @@ package main
import ( import (
"bytes" "bytes"
"cmp"
"compress/gzip" "compress/gzip"
"context" "context"
"io" "io"
@@ -15,7 +16,7 @@ import (
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"time" "time"
) )
@@ -177,8 +178,8 @@ func (d *diskStore) inventory() error {
}) })
return nil return nil
}) })
sort.Slice(d.currentFiles, func(i, j int) bool { slices.SortFunc(d.currentFiles, func(a, b currentFile) int {
return d.currentFiles[i].mtime < d.currentFiles[j].mtime return cmp.Compare(a.mtime, b.mtime)
}) })
var oldest time.Duration var oldest time.Duration
if len(d.currentFiles) > 0 { if len(d.currentFiles) > 0 {
+2 -2
View File
@@ -24,7 +24,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime/pprof" "runtime/pprof"
"sort" "slices"
"strconv" "strconv"
"syscall" "syscall"
"text/tabwriter" "text/tabwriter"
@@ -338,7 +338,7 @@ func debugFacilities() string {
maxLen = len(name) maxLen = len(name)
} }
} }
sort.Strings(names) slices.Sort(names)
// Format the choices // Format the choices
b := new(bytes.Buffer) b := new(bytes.Buffer)
+3 -1
View File
@@ -2,7 +2,7 @@
"A device with that ID is already added.": "أضيف هذا الجهاز بالفعل.", "A device with that ID is already added.": "أضيف هذا الجهاز بالفعل.",
"A negative number of days doesn't make sense.": "لا يمكن استخدام قيمة سالبة لعدد الأيام.", "A negative number of days doesn't make sense.": "لا يمكن استخدام قيمة سالبة لعدد الأيام.",
"A new major version may not be compatible with previous versions.": "الإصدار الجديد قد لا يتوافق مع الإصدارات السابقة.", "A new major version may not be compatible with previous versions.": "الإصدار الجديد قد لا يتوافق مع الإصدارات السابقة.",
"API Key": "مفتاح API", "API Key": "مفتاح واجهة برمجة التطبيقات \"API\"",
"About": "حول", "About": "حول",
"Action": "إجراء", "Action": "إجراء",
"Actions": "الإجراءات", "Actions": "الإجراءات",
@@ -27,6 +27,7 @@
"Allowed Networks": "الشبكات المسموح بها", "Allowed Networks": "الشبكات المسموح بها",
"Alphabetic": "أبجدية", "Alphabetic": "أبجدية",
"Altered by ignoring deletes.": "تغير بتجاهل عمليات الحذف.", "Altered by ignoring deletes.": "تغير بتجاهل عمليات الحذف.",
"Always turned on when the folder type is \"{%foldertype%}\".": "مفعل دائمًا عندما يكون نوع المجلد هو \"{{foldertype}}\".",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "الإصدار يعالج بواسطة أمر خارجي. يجب إزالة الملف من المجلدات المشتركة. إذا كان المسار للتطبيق يحتوي على مسافات، يجب وضعها بين علامتي تنصيص دلالة على الاقتباس.", "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "الإصدار يعالج بواسطة أمر خارجي. يجب إزالة الملف من المجلدات المشتركة. إذا كان المسار للتطبيق يحتوي على مسافات، يجب وضعها بين علامتي تنصيص دلالة على الاقتباس.",
"Anonymous Usage Reporting": "تقارير الإستخدام المجهولة", "Anonymous Usage Reporting": "تقارير الإستخدام المجهولة",
"Anonymous usage report format has changed. Would you like to move to the new format?": "هل تريد الانتقال الى التصميم الجديد لتقرير الاستخدام المجهول ؟", "Anonymous usage report format has changed. Would you like to move to the new format?": "هل تريد الانتقال الى التصميم الجديد لتقرير الاستخدام المجهول ؟",
@@ -52,6 +53,7 @@
"Body:": "جسم:", "Body:": "جسم:",
"Bugs": "أخطاء برمجية", "Bugs": "أخطاء برمجية",
"Cancel": "إلغاء", "Cancel": "إلغاء",
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "لا يمكن تفعيله عندما يكون نوع المجلد هو \"{{foldertype}}\".",
"Changelog": "سجل التغيير", "Changelog": "سجل التغيير",
"Clean out after": "نظف بعد", "Clean out after": "نظف بعد",
"Cleaning Versions": "إصدارات نظيفة", "Cleaning Versions": "إصدارات نظيفة",
+1
View File
@@ -26,6 +26,7 @@
"Allow Anonymous Usage Reporting?": "Permiteţi raportarea anonimă de folosire a aplicaţiei?", "Allow Anonymous Usage Reporting?": "Permiteţi raportarea anonimă de folosire a aplicaţiei?",
"Allowed Networks": "Rețele permise", "Allowed Networks": "Rețele permise",
"Alphabetic": "Alfabetic", "Alphabetic": "Alfabetic",
"Altered by ignoring deletes.": "Modificat prin ignorarea ștergerilor.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "O comandă externă gestionează versiunea. Trebuie să elimine fișierul din mapa partajat. Dacă calea către aplicație conține spații, ar trebui să fie pusă între ghilimele.", "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "O comandă externă gestionează versiunea. Trebuie să elimine fișierul din mapa partajat. Dacă calea către aplicație conține spații, ar trebui să fie pusă între ghilimele.",
"Anonymous Usage Reporting": "Raport Anonim despre Folosirea Aplicației", "Anonymous Usage Reporting": "Raport Anonim despre Folosirea Aplicației",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formatul raportului de utilizare anonim s-a schimbat. Doriți să vă mutați în noul format?", "Anonymous usage report format has changed. Would you like to move to the new format?": "Formatul raportului de utilizare anonim s-a schimbat. Doriți să vă mutați în noul format?",
+34
View File
@@ -1,2 +1,36 @@
{ {
"A device with that ID is already added.": "Уређај са тим идентификатором је већ додат.",
"A negative number of days doesn't make sense.": "Негативан број дана нема смисла.",
"A new major version may not be compatible with previous versions.": "Нова верзија можда неће радити са претходним верзијама.",
"API Key": "АПИ кључ",
"About": "Информације",
"Action": "Радња",
"Actions": "Радње",
"Active filter rules": "Активна правила филтера",
"Add": "Додај",
"Add Device": "Додај уређај",
"Add Folder": "Додај фасциклу",
"Add Remote Device": "Додаај удаљени уређај",
"Add devices from the introducer to our device list, for mutually shared folders.": "Додај уређаје од иницијатора на нашу листу уређаја, за међусобно дељене фасцикле.",
"Add filter entry": "Додај ставку филтера",
"Add ignore patterns": "Додај правила за игнорисање",
"Add new folder?": "Додај нову фасциклу?",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Додатно, интервал потпуног поновног скенирања ће бити повећан (60 пута, тј. нови подразумевани интервал од 1 сат). Такође можете ручно да га подесите за сваку фасциклу касније након што изаберете Не.",
"Address": "Адреса",
"Addresses": "Адресе",
"Advanced": "Напредно",
"Advanced Configuration": "Напредна конфигурација",
"All Data": "Сви подаци",
"All Time": "Све време",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Све фасцикле које се деле са овим уређајем морају бити заштићене лозинком, тако да сви послати подаци не могу бити прочитани без дате лозинке.",
"Allow Anonymous Usage Reporting?": "Дозволити анонимно слање података о коришћењу?",
"Allowed Networks": "Дозвољене мреже",
"Alphabetic": "Абецедним редом",
"Altered by ignoring deletes.": "Промењено због игнорисања брисања.",
"Always turned on when the folder type is \"{%foldertype%}\".": "Увек укључено када је тип фасцикле „{{foldertype}}\".",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Екстерна команда управља верзионирањем. Она мора да уклони фајл из дељене фасцикле. Ако путања до апликације садржи размаке, треба да буде под наводницима.",
"Anonymous Usage Reporting": "Анонимно слање података о употреби",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимног слања података о коришћењу је промењен. Желите ли да пређете на нови формат?",
"Applied to LAN": "Важи за локалну мрежу",
"Apply": "Примени"
} }
+32 -20
View File
@@ -1,27 +1,39 @@
angular.module('syncthing.core') angular.module('syncthing.core')
.filter('uncamel', function () { .filter('uncamel', function () {
const reservedStrings = [
'IDs', 'ID', // substrings must come AFTER longer keywords containing them
'URL', 'UR',
'API', 'QUIC', 'TCP', 'UDP', 'NAT', 'LAN', 'WAN',
'KiB', 'MiB', 'GiB', 'TiB'
];
return function (input) { return function (input) {
input = input.replace(/(.)([A-Z][a-z]+)/g, '$1 $2').replace(/([a-z0-9])([A-Z])/g, '$1 $2'); if (!input || typeof input !== 'string') return '';
var parts = input.split(' '); const placeholders = {};
var lastPart = parts.splice(-1)[0]; let counter = 0;
reservedStrings.forEach(word => {
const placeholder = `__RSV${counter}__`;
const re = new RegExp(word, 'g');
input = input.replace(re, placeholder);
placeholders[placeholder] = word;
counter++;
});
input = input.replace(/([a-z0-9])([A-Z])/g, '$1 $2');
Object.entries(placeholders).forEach(([ph, word]) => {
input = input.replace(new RegExp(ph, 'g'), ` ${word} `);
});
let parts = input.split(' ');
const lastPart = parts.pop();
switch (lastPart) { switch (lastPart) {
case "S": case 'S': parts.push('(seconds)'); break;
parts.push('(seconds)'); case 'M': parts.push('(minutes)'); break;
break; case 'H': parts.push('(hours)'); break;
case "M": case 'Ms': parts.push('(milliseconds)'); break;
parts.push('(minutes)'); default: parts.push(lastPart); break;
break;
case "H":
parts.push('(hours)');
break;
case "Ms":
parts.push('(milliseconds)');
break;
default:
parts.push(lastPart);
break;
} }
input = parts.join(' '); parts = parts.map(part => {
return input.charAt(0).toUpperCase() + input.slice(1); const match = reservedStrings.find(w => w.toUpperCase() === part.toUpperCase());
return match || part.charAt(0).toUpperCase() + part.slice(1);
});
return parts.join(' ').replace(/\s+/g, ' ').trim();
}; };
}); });
+2 -2
View File
@@ -8,7 +8,7 @@ package olddb
import ( import (
"encoding/binary" "encoding/binary"
"sort" "slices"
"github.com/syncthing/syncthing/internal/db/olddb/backend" "github.com/syncthing/syncthing/internal/db/olddb/backend"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
@@ -104,6 +104,6 @@ func (i *smallIndex) Values() []string {
} }
i.mut.Unlock() i.mut.Unlock()
sort.Strings(vals) slices.Sort(vals)
return vals return vals
} }
+8 -7
View File
@@ -8,6 +8,7 @@ package api
import ( import (
"bytes" "bytes"
"cmp"
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
@@ -24,7 +25,7 @@ import (
"reflect" "reflect"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -750,7 +751,7 @@ func (*service) getSystemVersion(w http.ResponseWriter, _ *http.Request) {
func (*service) getSystemDebug(w http.ResponseWriter, _ *http.Request) { func (*service) getSystemDebug(w http.ResponseWriter, _ *http.Request) {
names := l.Facilities() names := l.Facilities()
enabled := l.FacilityDebugging() enabled := l.FacilityDebugging()
sort.Strings(enabled) slices.Sort(enabled)
sendJSON(w, map[string]interface{}{ sendJSON(w, map[string]interface{}{
"facilities": names, "facilities": names,
"enabled": enabled, "enabled": enabled,
@@ -1516,8 +1517,8 @@ func (*service) getLang(w http.ResponseWriter, r *http.Request) {
langs = append(langs, code) langs = append(langs, code)
} }
// Reorder by descending q value // Reorder by descending q value
sort.SliceStable(langs, func(i, j int) bool { slices.SortStableFunc(langs, func(i, j string) int {
return weights[langs[i]] > weights[langs[j]] return cmp.Compare(weights[j], weights[i])
}) })
sendJSON(w, langs) sendJSON(w, langs)
} }
@@ -1803,8 +1804,8 @@ func browseFiles(ffs fs.Filesystem, search string) []string {
} }
// sort to return matches in deterministic order (don't depend on file system order) // sort to return matches in deterministic order (don't depend on file system order)
sort.Strings(exactMatches) slices.Sort(exactMatches)
sort.Strings(caseInsMatches) slices.Sort(caseInsMatches)
return append(exactMatches, caseInsMatches...) return append(exactMatches, caseInsMatches...)
} }
@@ -1901,7 +1902,7 @@ func dirNames(dir string) []string {
} }
} }
sort.Strings(dirs) slices.Sort(dirs)
return dirs return dirs
} }
+2 -2
View File
@@ -12,7 +12,7 @@ import (
"os" "os"
"regexp" "regexp"
"runtime" "runtime"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -127,7 +127,7 @@ func TagsList() []string {
} }
} }
sort.Strings(tags) slices.Sort(tags)
// Remove any empty tags, which will be at the front of the list now // Remove any empty tags, which will be at the front of the list now
for len(tags) > 0 && tags[0] == "" { for len(tags) > 0 && tags[0] == "" {
+3 -2
View File
@@ -17,6 +17,7 @@ import (
"net/url" "net/url"
"os" "os"
"reflect" "reflect"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -380,8 +381,8 @@ func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices
} }
} }
// Ensure that the folder list is sorted by ID // Ensure that the folder list is sorted by ID
sort.Slice(cfg.Folders, func(a, b int) bool { slices.SortFunc(cfg.Folders, func(a, b FolderConfiguration) int {
return cfg.Folders[a].ID < cfg.Folders[b].ID return strings.Compare(a.ID, b.ID)
}) })
return sharedFolders, nil return sharedFolders, nil
} }
+2 -2
View File
@@ -17,7 +17,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"sort" "slices"
"strings" "strings"
"testing" "testing"
@@ -911,7 +911,7 @@ func TestV14ListenAddressesMigration(t *testing.T) {
t.Error("Configuration was not converted") t.Error("Configuration was not converted")
} }
sort.Strings(tc[2]) slices.Sort(tc[2])
if !reflect.DeepEqual(cfg.Options.RawListenAddresses, tc[2]) { if !reflect.DeepEqual(cfg.Options.RawListenAddresses, tc[2]) {
t.Errorf("Migration error; actual %#v != expected %#v", cfg.Options.RawListenAddresses, tc[2]) t.Errorf("Migration error; actual %#v != expected %#v", cfg.Options.RawListenAddresses, tc[2])
} }
+5 -4
View File
@@ -7,11 +7,12 @@
package config package config
import ( import (
"cmp"
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"sort" "slices"
"strings" "strings"
"sync" "sync"
@@ -66,8 +67,8 @@ type migrationSet []migration
func (ms migrationSet) apply(cfg *Configuration) { func (ms migrationSet) apply(cfg *Configuration) {
// Make sure we apply the migrations in target version order regardless // Make sure we apply the migrations in target version order regardless
// of how it was defined. // of how it was defined.
sort.Slice(ms, func(a, b int) bool { slices.SortFunc(ms, func(a, b migration) int {
return ms[a].targetVersion < ms[b].targetVersion return cmp.Compare(a.targetVersion, b.targetVersion)
}) })
// Apply all migrations. // Apply all migrations.
@@ -354,7 +355,7 @@ func migrateToConfigV14(cfg *Configuration) {
cfg.Options.DeprecatedRelayServers = nil cfg.Options.DeprecatedRelayServers = nil
// For consistency // For consistency
sort.Strings(cfg.Options.RawListenAddresses) slices.Sort(cfg.Options.RawListenAddresses)
var newAddrs []string var newAddrs []string
for _, addr := range cfg.Options.RawGlobalAnnServers { for _, addr := range cfg.Options.RawGlobalAnnServers {
+4 -3
View File
@@ -9,7 +9,8 @@ package config
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"sort" "slices"
"strings"
"github.com/syncthing/syncthing/lib/structutil" "github.com/syncthing/syncthing/lib/structutil"
) )
@@ -84,8 +85,8 @@ func (c *VersioningConfiguration) toInternal() internalVersioningConfiguration {
for k, v := range c.Params { for k, v := range c.Params {
tmp.Params = append(tmp.Params, internalParam{k, v}) tmp.Params = append(tmp.Params, internalParam{k, v})
} }
sort.Slice(tmp.Params, func(a, b int) bool { slices.SortFunc(tmp.Params, func(a, b internalParam) int {
return tmp.Params[a].Key < tmp.Params[b].Key return strings.Compare(a.Key, b.Key)
}) })
return tmp return tmp
} }
+1 -2
View File
@@ -23,7 +23,6 @@ import (
"net" "net"
"net/url" "net/url"
"slices" "slices"
"sort"
"strings" "strings"
stdsync "sync" stdsync "sync"
"time" "time"
@@ -1151,7 +1150,7 @@ func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID,
} }
// Sort the priorities so that we dial lowest first (which means highest...) // Sort the priorities so that we dial lowest first (which means highest...)
sort.Ints(priorities) slices.Sort(priorities)
sema := semaphore.MultiSemaphore{semaphore.New(dialMaxParallelPerDevice), parentSema} sema := semaphore.MultiSemaphore{semaphore.New(dialMaxParallelPerDevice), parentSema}
for _, prio := range priorities { for _, prio := range priorities {
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"sort" "slices"
"time" "time"
"github.com/thejerf/suture/v4" "github.com/thejerf/suture/v4"
@@ -159,7 +159,7 @@ func (m *manager) Lookup(ctx context.Context, deviceID protocol.DeviceID) (addre
m.mut.RUnlock() m.mut.RUnlock()
addresses = stringutil.UniqueTrimmedStrings(addresses) addresses = stringutil.UniqueTrimmedStrings(addresses)
sort.Strings(addresses) slices.Sort(addresses)
l.Debugln("lookup results for", deviceID) l.Debugln("lookup results for", deviceID)
l.Debugln(" addresses: ", addresses) l.Debugln(" addresses: ", addresses)
+6 -7
View File
@@ -12,7 +12,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@@ -218,7 +218,7 @@ func TestDirNames(t *testing.T) {
"a", "a",
"bC", "bC",
} }
sort.Strings(testCases) slices.Sort(testCases)
for _, sub := range testCases { for _, sub := range testCases {
if err := os.Mkdir(filepath.Join(dir, sub), 0o777); err != nil { if err := os.Mkdir(filepath.Join(dir, sub), 0o777); err != nil {
@@ -229,7 +229,7 @@ func TestDirNames(t *testing.T) {
if dirs, err := fs.DirNames("."); err != nil || len(dirs) != len(testCases) { if dirs, err := fs.DirNames("."); err != nil || len(dirs) != len(testCases) {
t.Errorf("%s %s %s", err, dirs, testCases) t.Errorf("%s %s %s", err, dirs, testCases)
} else { } else {
sort.Strings(dirs) slices.Sort(dirs)
for i := range dirs { for i := range dirs {
if dirs[i] != testCases[i] { if dirs[i] != testCases[i] {
t.Errorf("%s != %s", dirs[i], testCases[i]) t.Errorf("%s != %s", dirs[i], testCases[i])
@@ -321,8 +321,8 @@ func TestGlob(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
results, err := fs.Glob(testCase.pattern) results, err := fs.Glob(testCase.pattern)
sort.Strings(results) slices.Sort(results)
sort.Strings(testCase.matches) slices.Sort(testCase.matches)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -628,8 +628,7 @@ func TestXattr(t *testing.T) {
Value: value, Value: value,
}) })
} }
sort.Slice(attrs, func(i, j int) bool { return attrs[i].Name < attrs[j].Name }) slices.SortFunc(attrs, func(a, b protocol.Xattr) int { return strings.Compare(a.Name, b.Name) })
// Set the xattrs, read them back and compare // Set the xattrs, read them back and compare
if err := tfs.SetXattr("/test", attrs, testXattrFilter{}); err != nil { if err := tfs.SetXattr("/test", attrs, testXattrFilter{}); err != nil {
t.Fatal(err) t.Fatal(err)
+2 -2
View File
@@ -12,7 +12,7 @@ package fs
import ( import (
"errors" "errors"
"fmt" "fmt"
"sort" "slices"
"unsafe" "unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -69,7 +69,7 @@ func listXattr(path string) ([]string, error) {
} }
} }
sort.Strings(attrs) slices.Sort(attrs)
return attrs, nil return attrs, nil
} }
+2 -2
View File
@@ -12,7 +12,7 @@ package fs
import ( import (
"errors" "errors"
"fmt" "fmt"
"sort" "slices"
"strings" "strings"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -38,6 +38,6 @@ func listXattr(path string) ([]string, error) {
buf = buf[:size] buf = buf[:size]
attrs := compact(strings.Split(string(buf), "\x00")) attrs := compact(strings.Split(string(buf), "\x00"))
sort.Strings(attrs) slices.Sort(attrs)
return attrs, nil return attrs, nil
} }
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort" "slices"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -344,7 +344,7 @@ func fakefsForBenchmark(nfiles int, latency time.Duration) (Filesystem, []string
return nil, nil, errors.New("didn't find enough stuff") return nil, nil, errors.New("didn't find enough stuff")
} }
sort.Strings(paths) slices.Sort(paths)
return fsys, paths, nil return fsys, paths, nil
} }
+3 -3
View File
@@ -14,7 +14,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort" "slices"
"testing" "testing"
"time" "time"
@@ -369,8 +369,8 @@ func assertDir(t *testing.T, fs Filesystem, directory string, filenames []string
if path.Clean(directory) == "/" { if path.Clean(directory) == "/" {
filenames = append(filenames, ".stfolder") filenames = append(filenames, ".stfolder")
} }
sort.Strings(filenames) slices.Sort(filenames)
sort.Strings(got) slices.Sort(got)
if len(filenames) != len(got) { if len(filenames) != len(got) {
t.Errorf("want %s, got %s", filenames, got) t.Errorf("want %s, got %s", filenames, got)
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"path/filepath" "path/filepath"
"slices"
"sort" "sort"
"time" "time"
@@ -1362,7 +1363,7 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string {
if len(dirs) == 0 { if len(dirs) == 0 {
return nil return nil
} }
sort.Strings(dirs) slices.Sort(dirs)
if dirs[0] == "" || dirs[0] == "." || dirs[0] == string(fs.PathSeparator) { if dirs[0] == "" || dirs[0] == "." || dirs[0] == string(fs.PathSeparator) {
return nil return nil
} }
+2 -3
View File
@@ -19,7 +19,6 @@ import (
"path/filepath" "path/filepath"
"runtime/pprof" "runtime/pprof"
"slices" "slices"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -3992,8 +3991,8 @@ func equalStringsInAnyOrder(a, b []string) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false
} }
sort.Strings(a) slices.Sort(a)
sort.Strings(b) slices.Sort(b)
for i := range a { for i := range a {
if a[i] != b[i] { if a[i] != b[i] {
return false return false
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"sort" "slices"
"sync" "sync"
"time" "time"
@@ -166,7 +166,7 @@ func relayAddressesOrder(ctx context.Context, input []string) []string {
ids = append(ids, id) ids = append(ids, id)
} }
sort.Ints(ids) slices.Sort(ids)
addresses := make([]string, 0, len(input)) addresses := make([]string, 0, len(input))
for _, id := range ids { for _, id := range ids {
+3 -3
View File
@@ -15,7 +15,7 @@ import (
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"sort" "slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -436,8 +436,8 @@ func printServiceTree(w io.Writer, sup supervisor, level int) {
printService(w, sup, level) printService(w, sup, level)
svcs := sup.Services() svcs := sup.Services()
sort.Slice(svcs, func(a, b int) bool { slices.SortFunc(svcs, func(a, b suture.Service) int {
return fmt.Sprint(svcs[a]) < fmt.Sprint(svcs[b]) return strings.Compare(fmt.Sprint(a), fmt.Sprint(b))
}) })
for _, svc := range svcs { for _, svc := range svcs {
+3 -3
View File
@@ -16,7 +16,7 @@ import (
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"sort" "slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -160,7 +160,7 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (
l.Warnf("Unhandled versioning type for usage reports: %s", cfg.Versioning.Type) l.Warnf("Unhandled versioning type for usage reports: %s", cfg.Versioning.Type)
} }
} }
sort.Ints(report.RescanIntvs) slices.Sort(report.RescanIntvs)
for _, cfg := range s.cfg.Devices() { for _, cfg := range s.cfg.Devices() {
if cfg.Introducer { if cfg.Introducer {
@@ -288,7 +288,7 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (
report.FolderUsesV3.SyncOwnership++ report.FolderUsesV3.SyncOwnership++
} }
} }
sort.Ints(report.FolderUsesV3.FsWatcherDelays) slices.Sort(report.FolderUsesV3.FsWatcherDelays)
for _, cfg := range s.cfg.Devices() { for _, cfg := range s.cfg.Devices() {
if cfg.Untrusted { if cfg.Untrusted {
+2 -2
View File
@@ -8,7 +8,7 @@ package versioner
import ( import (
"context" "context"
"sort" "slices"
"strconv" "strconv"
"time" "time"
@@ -79,7 +79,7 @@ func (v simple) toRemove(versions []string, now time.Time) []string {
var remove []string var remove []string
// The list of versions may or may not be properly sorted. // The list of versions may or may not be properly sorted.
sort.Strings(versions) slices.Sort(versions)
// If the amount of elements exceeds the limit: the oldest elements are to be removed. // If the amount of elements exceeds the limit: the oldest elements are to be removed.
if len(versions) > v.keep { if len(versions) > v.keep {
+2 -2
View File
@@ -9,7 +9,7 @@ package versioner
import ( import (
"context" "context"
"fmt" "fmt"
"sort" "slices"
"strconv" "strconv"
"time" "time"
@@ -69,7 +69,7 @@ func (v *staggered) toRemove(versions []string, now time.Time) []string {
var remove []string var remove []string
// The list of versions may or may not be properly sorted. // The list of versions may or may not be properly sorted.
sort.Strings(versions) slices.Sort(versions)
for _, version := range versions { for _, version := range versions {
versionTime, err := time.ParseInLocation(TimeFormat, extractTag(version), time.Local) versionTime, err := time.ParseInLocation(TimeFormat, extractTag(version), time.Local)
+3 -3
View File
@@ -9,7 +9,7 @@ package versioner
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@@ -97,7 +97,7 @@ func TestStaggeredVersioningVersionCount(t *testing.T) {
"test~20150416-135958", // 365 days 2 seconds ago "test~20150416-135958", // 365 days 2 seconds ago
"test~20150414-140000", // 367 days ago "test~20150414-140000", // 367 days ago
} }
sort.Strings(delete) slices.Sort(delete)
cfg := config.FolderConfiguration{ cfg := config.FolderConfiguration{
FilesystemType: config.FilesystemTypeBasic, FilesystemType: config.FilesystemTypeBasic,
@@ -111,7 +111,7 @@ func TestStaggeredVersioningVersionCount(t *testing.T) {
v := newStaggered(cfg).(*staggered) v := newStaggered(cfg).(*staggered)
rem := v.toRemove(versionsWithMtime, now) rem := v.toRemove(versionsWithMtime, now)
sort.Strings(rem) slices.Sort(rem)
if diff, equal := messagediff.PrettyDiff(delete, rem); !equal { if diff, equal := messagediff.PrettyDiff(delete, rem); !equal {
t.Errorf("Incorrect deleted files; got %v, expected %v\n%v", rem, delete, diff) t.Errorf("Incorrect deleted files; got %v, expected %v\n%v", rem, delete, diff)
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort" "slices"
"strings" "strings"
"time" "time"
@@ -339,7 +339,7 @@ func findAllVersions(fs fs.Filesystem, filePath string) []string {
return nil return nil
} }
versions = stringutil.UniqueTrimmedStrings(versions) versions = stringutil.UniqueTrimmedStrings(versions)
sort.Strings(versions) slices.Sort(versions)
return versions return versions
} }
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "STDISCOSRV" "1" "May 15, 2025" "v1.29.6" "Syncthing" .TH "STDISCOSRV" "1" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
stdiscosrv \- Syncthing Discovery Server stdiscosrv \- Syncthing Discovery Server
.SH SYNOPSIS .SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "STRELAYSRV" "1" "May 15, 2025" "v1.29.6" "Syncthing" .TH "STRELAYSRV" "1" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
strelaysrv \- Syncthing Relay Server strelaysrv \- Syncthing Relay Server
.SH SYNOPSIS .SH SYNOPSIS
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-BEP" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-BEP" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-bep \- Block Exchange Protocol v1 syncthing-bep \- Block Exchange Protocol v1
.SH INTRODUCTION AND DEFINITIONS .SH INTRODUCTION AND DEFINITIONS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-CONFIG" "5" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-CONFIG" "5" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-config \- Syncthing Configuration syncthing-config \- Syncthing Configuration
.SH SYNOPSIS .SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-DEVICE-IDS" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-DEVICE-IDS" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-device-ids \- Understanding Device IDs syncthing-device-ids \- Understanding Device IDs
.sp .sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-EVENT-API" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-EVENT-API" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-event-api \- Event API syncthing-event-api \- Event API
.SH DESCRIPTION .SH DESCRIPTION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-FAQ" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-FAQ" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-faq \- Frequently Asked Questions syncthing-faq \- Frequently Asked Questions
.INDENT 0.0 .INDENT 0.0
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-GLOBALDISCO" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-GLOBALDISCO" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3 syncthing-globaldisco \- Global Discovery Protocol v3
.SH ANNOUNCEMENTS .SH ANNOUNCEMENTS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-LOCALDISCO" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-LOCALDISCO" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-localdisco \- Local Discovery Protocol v4 syncthing-localdisco \- Local Discovery Protocol v4
.SH MODE OF OPERATION .SH MODE OF OPERATION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-NETWORKING" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-NETWORKING" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-networking \- Firewall Setup syncthing-networking \- Firewall Setup
.SH ROUTER SETUP .SH ROUTER SETUP
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-RELAY" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-RELAY" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-relay \- Relay Protocol v1 syncthing-relay \- Relay Protocol v1
.SH WHAT IS A RELAY? .SH WHAT IS A RELAY?
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-REST-API" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-REST-API" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-rest-api \- REST API syncthing-rest-api \- REST API
.sp .sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-SECURITY" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-SECURITY" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-security \- Security Principles syncthing-security \- Security Principles
.sp .sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-STIGNORE" "5" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-STIGNORE" "5" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes syncthing-stignore \- Prevent files from being synchronized to other nodes
.SH SYNOPSIS .SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING-VERSIONING" "7" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING-VERSIONING" "7" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.sp .sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "SYNCTHING" "1" "May 15, 2025" "v1.29.6" "Syncthing" .TH "SYNCTHING" "1" "May 25, 2025" "v1.29.6" "Syncthing"
.SH NAME .SH NAME
syncthing \- Syncthing syncthing \- Syncthing
.SH SYNOPSIS .SH SYNOPSIS
+18
View File
@@ -0,0 +1,18 @@
# Release Notes
Files in this directory constitute manual release notes for a given release.
When relevant, they should be created prior to that release so that they can
be included in the corresponding tag message, etc.
To add release notes for a release 1.2.3, create a file named `v1.2.3.md`
consisting of an initial H2-level header and further notes as desired. For
example:
```
## Major changes in v1.2.3
- Files are now synchronized twice as fast on Tuesdays
```
The release notes will also be included in candidate releases (e.g.
v1.2.3-rc.1).
+109
View File
@@ -0,0 +1,109 @@
// Copyright (C) 2025 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build ignore
// +build ignore
package main
import (
"bytes"
"cmp"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
)
var (
githubToken = os.Getenv("GITHUB_TOKEN")
githubRepo = cmp.Or(os.Getenv("GITHUB_REPOSITORY"), "syncthing/syncthing")
)
func main() {
ver := flag.String("new-ver", "", "New version tag")
prevVer := flag.String("prev-ver", "", "Previous version tag")
branch := flag.String("branch", "HEAD", "Branch to release from")
flag.Parse()
log.SetOutput(os.Stderr)
if *ver == "" {
log.Fatalln("Must set --new-ver")
}
if githubToken == "" {
log.Fatalln("Must set $GITHUB_TOKEN")
}
addl, err := additionalNotes(*ver)
if err != nil {
log.Fatalln("Gathering additional notes:", err)
}
notes, err := generatedNotes(*ver, *branch, *prevVer)
if err != nil {
log.Fatalln("Gathering github notes:", err)
}
if addl != "" {
fmt.Println(addl)
}
fmt.Println(notes)
}
// Load potential additional release notes from within the repo
func additionalNotes(newVer string) (string, error) {
ver, _, _ := strings.Cut(newVer, "-")
bs, err := os.ReadFile(fmt.Sprintf("relnotes/%s.md", ver))
if os.IsNotExist(err) {
return "", nil
}
return string(bs), err
}
// Load generated release notes (list of pull requests and contributors)
// from GitHub.
func generatedNotes(newVer, targetCommit, prevVer string) (string, error) {
fields := map[string]string{
"tag_name": newVer,
"target_commitish": targetCommit,
"previous_tag_name": prevVer,
}
bs, err := json.Marshal(fields)
if err != nil {
return "", err
}
req, err := http.NewRequest(http.MethodPost, "https://api.github.com/repos/"+githubRepo+"/releases/generate-notes", bytes.NewReader(bs)) //nolint:noctx
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("Authorization", "Bearer "+githubToken)
req.Header.Set("X-Github-Api-Version", "2022-11-28")
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
if res.StatusCode != http.StatusOK {
bs, _ := io.ReadAll(res.Body)
log.Print(string(bs))
return "", errors.New(res.Status) //nolint:err113
}
defer res.Body.Close()
var resJSON struct {
Body string
}
if err := json.NewDecoder(res.Body).Decode(&resJSON); err != nil {
return "", err
}
return resJSON.Body, nil
}
+2 -2
View File
@@ -17,7 +17,7 @@ import (
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
"sort" "slices"
"strings" "strings"
) )
@@ -93,7 +93,7 @@ func main() {
} }
func saveValidLangs(langs []string) { func saveValidLangs(langs []string) {
sort.Strings(langs) slices.Sort(langs)
fd, err := os.Create("valid-langs.js") fd, err := os.Create("valid-langs.js")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
+2 -2
View File
@@ -17,7 +17,7 @@ import (
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
"sort" "slices"
"strings" "strings"
) )
@@ -116,7 +116,7 @@ func reformatLanguageCode(origCode string) string {
} }
func saveValidLangs(langs []string) { func saveValidLangs(langs []string) {
sort.Strings(langs) slices.Sort(langs)
fd, err := os.Create("valid-langs.js") fd, err := os.Create("valid-langs.js")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)