diff --git a/.deadcode-out b/.deadcode-out index 403a6e40d2..b17d738bd6 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -87,6 +87,9 @@ code.gitea.io/gitea/modules/eventsource Event.String code.gitea.io/gitea/modules/forgefed + NewForgeUndoLike + ForgeUndoLike.UnmarshalJSON + ForgeUndoLike.Validate GetItemByType JSONUnmarshalerFn NotEmpty @@ -103,9 +106,6 @@ code.gitea.io/gitea/modules/git openRepositoryWithDefaultContext ToEntryMode -code.gitea.io/gitea/modules/gitgraph - Parser.Reset - code.gitea.io/gitea/modules/gitrepo GetBranchCommitID GetWikiDefaultBranch @@ -224,6 +224,9 @@ code.gitea.io/gitea/services/repository code.gitea.io/gitea/services/repository/files ContentType.String +code.gitea.io/gitea/services/repository/gitgraph + Parser.Reset + code.gitea.io/gitea/services/webhook NewNotifier diff --git a/go.mod b/go.mod index f8d163a492..0746c4ab42 100644 --- a/go.mod +++ b/go.mod @@ -103,7 +103,7 @@ require ( go.uber.org/mock v0.4.0 golang.org/x/crypto v0.33.0 golang.org/x/image v0.23.0 - golang.org/x/net v0.34.0 + golang.org/x/net v0.35.0 golang.org/x/oauth2 v0.24.0 golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 diff --git a/go.sum b/go.sum index 9f891097f3..1df46c9df9 100644 --- a/go.sum +++ b/go.sum @@ -1640,8 +1640,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/modules/forgefed/activity.go b/modules/forgefed/activity_like.go similarity index 93% rename from modules/forgefed/activity.go rename to modules/forgefed/activity_like.go index 247abd255a..0f001486b5 100644 --- a/modules/forgefed/activity.go +++ b/modules/forgefed/activity_like.go @@ -21,8 +21,8 @@ type ForgeLike struct { func NewForgeLike(actorIRI, objectIRI string, startTime time.Time) (ForgeLike, error) { result := ForgeLike{} result.Type = ap.LikeType - result.Actor = ap.IRI(actorIRI) // That's us, a User - result.Object = ap.IRI(objectIRI) // That's them, a Repository + result.Actor = ap.IRI(actorIRI) + result.Object = ap.IRI(objectIRI) result.StartTime = startTime if valid, err := validation.IsValid(result); !valid { return ForgeLike{}, err @@ -46,20 +46,23 @@ func (like ForgeLike) Validate() []string { var result []string result = append(result, validation.ValidateNotEmpty(string(like.Type), "type")...) result = append(result, validation.ValidateOneOf(string(like.Type), []any{"Like"}, "type")...) + if like.Actor == nil { result = append(result, "Actor should not be nil.") } else { result = append(result, validation.ValidateNotEmpty(like.Actor.GetID().String(), "actor")...) } - if like.Object == nil { - result = append(result, "Object should not be nil.") - } else { - result = append(result, validation.ValidateNotEmpty(like.Object.GetID().String(), "object")...) - } + result = append(result, validation.ValidateNotEmpty(like.StartTime.String(), "startTime")...) if like.StartTime.IsZero() { result = append(result, "StartTime was invalid.") } + if like.Object == nil { + result = append(result, "Object should not be nil.") + } else { + result = append(result, validation.ValidateNotEmpty(like.Object.GetID().String(), "object")...) + } + return result } diff --git a/modules/forgefed/activity_test.go b/modules/forgefed/activity_like_test.go similarity index 80% rename from modules/forgefed/activity_test.go rename to modules/forgefed/activity_like_test.go index 9a7979c4e6..6b83381cf9 100644 --- a/modules/forgefed/activity_test.go +++ b/modules/forgefed/activity_like_test.go @@ -16,11 +16,11 @@ import ( ) func Test_NewForgeLike(t *testing.T) { + want := []byte(`{"type":"Like","startTime":"2024-03-07T00:00:00Z","actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1","object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}`) + actorIRI := "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1" objectIRI := "https://codeberg.org/api/v1/activitypub/repository-id/1" - want := []byte(`{"type":"Like","startTime":"2024-03-27T00:00:00Z","actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1","object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}`) - - startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-27") + startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-07") sut, err := NewForgeLike(actorIRI, objectIRI, startTime) if err != nil { t.Errorf("unexpected error: %v\n", err) @@ -84,7 +84,6 @@ func Test_LikeUnmarshalJSON(t *testing.T) { wantErr error } - //revive:disable tests := map[string]testPair{ "with ID": { item: []byte(`{"type":"Like","actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1","object":"https://codeberg.org/api/activitypub/repository-id/1"}`), @@ -100,10 +99,9 @@ func Test_LikeUnmarshalJSON(t *testing.T) { "invalid": { item: []byte(`{"type":"Invalid","actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1","object":"https://codeberg.org/api/activitypub/repository-id/1"`), want: &ForgeLike{}, - wantErr: fmt.Errorf("cannot parse JSON:"), + wantErr: fmt.Errorf("cannot parse JSON"), }, } - //revive:enable for name, test := range tests { t.Run(name, func(t *testing.T) { @@ -120,7 +118,9 @@ func Test_LikeUnmarshalJSON(t *testing.T) { } } -func TestActivityValidation(t *testing.T) { +func Test_ForgeLikeValidation(t *testing.T) { + // Successful + sut := new(ForgeLike) sut.UnmarshalJSON([]byte(`{"type":"Like", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", @@ -130,35 +130,37 @@ func TestActivityValidation(t *testing.T) { t.Errorf("sut expected to be valid: %v\n", sut.Validate()) } + // Errors + sut.UnmarshalJSON([]byte(`{"actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", "object":"https://codeberg.org/api/activitypub/repository-id/1", "startTime": "2014-12-31T23:00:00-08:00"}`)) - if sut.Validate()[0] != "type should not be empty" { - t.Errorf("validation error expected but was: %v\n", sut.Validate()[0]) + if err := validateAndCheckError(sut, "type should not be empty"); err != nil { + t.Error(err) } sut.UnmarshalJSON([]byte(`{"type":"bad-type", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", "object":"https://codeberg.org/api/activitypub/repository-id/1", "startTime": "2014-12-31T23:00:00-08:00"}`)) - if sut.Validate()[0] != "Value bad-type is not contained in allowed values [Like]" { - t.Errorf("validation error expected but was: %v\n", sut.Validate()[0]) + if err := validateAndCheckError(sut, "Value bad-type is not contained in allowed values [Like]"); err != nil { + t.Error(err) } sut.UnmarshalJSON([]byte(`{"type":"Like", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", - "object":"https://codeberg.org/api/activitypub/repository-id/1", - "startTime": "not a date"}`)) - if sut.Validate()[0] != "StartTime was invalid." { - t.Errorf("validation error expected but was: %v\n", sut.Validate()) + "object":"https://codeberg.org/api/activitypub/repository-id/1", + "startTime": "not a date"}`)) + if err := validateAndCheckError(sut, "StartTime was invalid."); err != nil { + t.Error(err) } sut.UnmarshalJSON([]byte(`{"type":"Wrong", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", - "object":"https://codeberg.org/api/activitypub/repository-id/1", - "startTime": "2014-12-31T23:00:00-08:00"}`)) - if sut.Validate()[0] != "Value Wrong is not contained in allowed values [Like]" { - t.Errorf("validation error expected but was: %v\n", sut.Validate()) + "object":"https://codeberg.org/api/activitypub/repository-id/1", + "startTime": "2014-12-31T23:00:00-08:00"}`)) + if err := validateAndCheckError(sut, "Value Wrong is not contained in allowed values [Like]"); err != nil { + t.Error(err) } } @@ -166,6 +168,6 @@ func TestActivityValidation_Attack(t *testing.T) { sut := new(ForgeLike) sut.UnmarshalJSON([]byte(`{rubbish}`)) if len(sut.Validate()) != 5 { - t.Errorf("5 validateion errors expected but was: %v\n", len(sut.Validate())) + t.Errorf("5 validation errors expected but was: %v\n", len(sut.Validate())) } } diff --git a/modules/forgefed/activity_undo_like.go b/modules/forgefed/activity_undo_like.go new file mode 100644 index 0000000000..b6b13ba50d --- /dev/null +++ b/modules/forgefed/activity_undo_like.go @@ -0,0 +1,80 @@ +// Copyright 2023, 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgefed + +import ( + "time" + + "code.gitea.io/gitea/modules/validation" + + ap "github.com/go-ap/activitypub" +) + +// ForgeLike activity data type +// swagger:model +type ForgeUndoLike struct { + // swagger:ignore + ap.Activity +} + +func NewForgeUndoLike(actorIRI, objectIRI string, startTime time.Time) (ForgeUndoLike, error) { + result := ForgeUndoLike{} + result.Type = ap.UndoType + result.Actor = ap.IRI(actorIRI) + result.StartTime = startTime + + like := ap.Activity{} + like.Type = ap.LikeType + like.Actor = ap.IRI(actorIRI) + like.Object = ap.IRI(objectIRI) + result.Object = &like + + if valid, err := validation.IsValid(result); !valid { + return ForgeUndoLike{}, err + } + return result, nil +} + +func (undo *ForgeUndoLike) UnmarshalJSON(data []byte) error { + return undo.Activity.UnmarshalJSON(data) +} + +func (undo ForgeUndoLike) Validate() []string { + var result []string + result = append(result, validation.ValidateNotEmpty(string(undo.Type), "type")...) + result = append(result, validation.ValidateOneOf(string(undo.Type), []any{"Undo"}, "type")...) + + if undo.Actor == nil { + result = append(result, "Actor should not be nil.") + } else { + result = append(result, validation.ValidateNotEmpty(undo.Actor.GetID().String(), "actor")...) + } + + result = append(result, validation.ValidateNotEmpty(undo.StartTime.String(), "startTime")...) + if undo.StartTime.IsZero() { + result = append(result, "StartTime was invalid.") + } + + if undo.Object == nil { + result = append(result, "object should not be empty.") + } else if activity, ok := undo.Object.(*ap.Activity); !ok { + result = append(result, "object is not of type Activity") + } else { + result = append(result, validation.ValidateNotEmpty(string(activity.Type), "type")...) + result = append(result, validation.ValidateOneOf(string(activity.Type), []any{"Like"}, "type")...) + + if activity.Actor == nil { + result = append(result, "Object.Actor should not be nil.") + } else { + result = append(result, validation.ValidateNotEmpty(activity.Actor.GetID().String(), "actor")...) + } + + if activity.Object == nil { + result = append(result, "Object.Object should not be nil.") + } else { + result = append(result, validation.ValidateNotEmpty(activity.Object.GetID().String(), "object")...) + } + } + return result +} diff --git a/modules/forgefed/activity_undo_like_test.go b/modules/forgefed/activity_undo_like_test.go new file mode 100644 index 0000000000..541e524cb3 --- /dev/null +++ b/modules/forgefed/activity_undo_like_test.go @@ -0,0 +1,246 @@ +// Copyright 2023, 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgefed + +import ( + "fmt" + "reflect" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/modules/validation" + + ap "github.com/go-ap/activitypub" +) + +func Test_NewForgeUndoLike(t *testing.T) { + actorIRI := "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1" + objectIRI := "https://codeberg.org/api/v1/activitypub/repository-id/1" + want := []byte(`{"type":"Undo","startTime":"2024-03-27T00:00:00Z",` + + `"actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",` + + `"object":{` + + `"type":"Like",` + + `"actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",` + + `"object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`) + + startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-27") + sut, err := NewForgeUndoLike(actorIRI, objectIRI, startTime) + if err != nil { + t.Errorf("unexpected error: %v\n", err) + } + if valid, _ := validation.IsValid(sut); !valid { + t.Errorf("sut expected to be valid: %v\n", sut.Validate()) + } + + got, err := sut.MarshalJSON() + if err != nil { + t.Errorf("MarshalJSON() error = \"%v\"", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("MarshalJSON() got = %q, want %q", got, want) + } +} + +func Test_UndoLikeMarshalJSON(t *testing.T) { + type testPair struct { + item ForgeUndoLike + want []byte + wantErr error + } + + startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-27") + like, _ := NewForgeLike("https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", "https://codeberg.org/api/v1/activitypub/repository-id/1", startTime) + tests := map[string]testPair{ + "empty": { + item: ForgeUndoLike{}, + want: nil, + }, + "valid": { + item: ForgeUndoLike{ + Activity: ap.Activity{ + StartTime: startTime, + Actor: ap.IRI("https://repo.prod.meissa.de/api/v1/activitypub/user-id/1"), + Type: "Undo", + Object: like, + }, + }, + want: []byte(`{"type":"Undo",` + + `"startTime":"2024-03-27T00:00:00Z",` + + `"actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",` + + `"object":{` + + `"type":"Like",` + + `"startTime":"2024-03-27T00:00:00Z",` + + `"actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",` + + `"object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`), + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + got, err := tt.item.MarshalJSON() + if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() { + t.Errorf("MarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %q\nwant %q", got, tt.want) + } + }) + } +} + +func Test_UndoLikeUnmarshalJSON(t *testing.T) { + type testPair struct { + item []byte + want *ForgeUndoLike + wantErr error + } + + startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-27") + like, _ := NewForgeLike("https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", "https://codeberg.org/api/v1/activitypub/repository-id/1", startTime) + + tests := map[string]testPair{ + "valid": { + item: []byte(`{"type":"Undo",` + + `"startTime":"2024-03-27T00:00:00Z",` + + `"actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",` + + `"object":{` + + `"type":"Like",` + + `"startTime":"2024-03-27T00:00:00Z",` + + `"actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",` + + `"object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`), + want: &ForgeUndoLike{ + Activity: ap.Activity{ + StartTime: startTime, + Actor: ap.IRI("https://repo.prod.meissa.de/api/v1/activitypub/user-id/1"), + Type: "Undo", + Object: like, + }, + }, + wantErr: nil, + }, + "invalid": { + item: []byte(`invalid JSON`), + want: nil, + wantErr: fmt.Errorf("cannot parse JSON"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + got := new(ForgeUndoLike) + err := got.UnmarshalJSON(test.item) + if test.wantErr != nil { + if err == nil { + t.Errorf("UnmarshalJSON() error = nil, wantErr \"%v\"", test.wantErr) + } else if !strings.Contains(err.Error(), test.wantErr.Error()) { + t.Errorf("UnmarshalJSON() error = \"%v\", wantErr \"%v\"", err, test.wantErr) + } + return + } + remarshalledgot, _ := got.MarshalJSON() + remarshalledwant, _ := test.want.MarshalJSON() + if !reflect.DeepEqual(remarshalledgot, remarshalledwant) { + t.Errorf("UnmarshalJSON() got = %#v\nwant %#v", got, test.want) + } + }) + } +} + +func TestActivityValidationUndo(t *testing.T) { + sut := new(ForgeUndoLike) + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":{ + "type":"Like", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`)) + if res, _ := validation.IsValid(sut); !res { + t.Errorf("sut expected to be valid: %v\n", sut.Validate()) + } + + _ = sut.UnmarshalJSON([]byte(` + {"startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":{ + "type":"Like", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`)) + if err := validateAndCheckError(sut, "type should not be empty"); err != nil { + t.Error(*err) + } + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "object":{ + "type":"Like", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`)) + if err := validateAndCheckError(sut, "Actor should not be nil."); err != nil { + t.Error(*err) + } + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "actor":"string", + "object":{ + "type":"Like", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`)) + if err := validateAndCheckError(sut, "Actor should not be nil."); err != nil { + t.Error(*err) + } + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1" + }`)) + if err := validateAndCheckError(sut, "object should not be empty."); err != nil { + t.Error(*err) + } + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":{ + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`)) + if err := validateAndCheckError(sut, "object is not of type Activity"); err != nil { + t.Error(*err) + } + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":{ + "type":"Like", + "object":""}}`)) + if err := validateAndCheckError(sut, "Object.Actor should not be nil."); err != nil { + t.Error(*err) + } + + _ = sut.UnmarshalJSON([]byte(` + {"type":"Undo", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", + "object":{ + "type":"Like", + "startTime":"2024-03-27T00:00:00Z", + "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1"}}`)) + if err := validateAndCheckError(sut, "Object.Object should not be nil."); err != nil { + t.Error(*err) + } +} diff --git a/modules/forgefed/activity_validateandcheckerror_test.go b/modules/forgefed/activity_validateandcheckerror_test.go new file mode 100644 index 0000000000..f2f1fbcccb --- /dev/null +++ b/modules/forgefed/activity_validateandcheckerror_test.go @@ -0,0 +1,23 @@ +// Copyright 2023, 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgefed + +import ( + "fmt" + + "code.gitea.io/gitea/modules/validation" +) + +func validateAndCheckError(subject validation.Validateable, expectedError string) *string { + errors := subject.Validate() + err := errors[0] + if len(errors) < 1 { + val := "Validation error should have been returned, but was not." + return &val + } else if err != expectedError { + val := fmt.Sprintf("Validation error should be [%v] but was: %v\n", expectedError, err) + return &val + } + return nil +} diff --git a/options/gitignore/Flutter b/options/gitignore/Flutter new file mode 100644 index 0000000000..39b8814aec --- /dev/null +++ b/options/gitignore/Flutter @@ -0,0 +1,119 @@ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.buildlog/ +.history + + + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +**/generated_plugin_registrant.dart +.packages +.pub-preload-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake + +# Coverage +coverage/ + +# Symbols +app.*.symbols + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock \ No newline at end of file diff --git a/options/gitignore/Nix b/options/gitignore/Nix index 1fd04ef1f6..912e6700f4 100644 --- a/options/gitignore/Nix +++ b/options/gitignore/Nix @@ -1,3 +1,6 @@ # Ignore build outputs from performing a nix-build or `nix build` command result result-* + +# Ignore automatically generated direnv output +.direnv diff --git a/options/gitignore/NotesAndCoreConfiguration b/options/gitignore/NotesAndCoreConfiguration new file mode 100644 index 0000000000..4eff01dae1 --- /dev/null +++ b/options/gitignore/NotesAndCoreConfiguration @@ -0,0 +1,16 @@ +# Excludes Obsidian workspace cache and plugins. All notes and core obsidian +# configuration files are tracked by Git. + +# The current application UI state (DOM layout, recently-opened files, etc.) is +# stored in these files (separate for desktop and mobile) so you can resume +# your session seamlessly after a restart. If you want to track UI state, use +# the Workspaces core plugin instead of relying on these files. +.obsidian/workspace.json +.obsidian/workspace-mobile.json + +# Obsidian plugins are stored under .obsidian/plugins/$plugin_name. They +# contain metadata (manifest.json), application code (main.js), stylesheets +# (styles.css), and user-configuration data (data.json). +# We want to exclude all plugin-related files, so we can exclude everything +# under this directory. +.obsidian/plugins/**/* diff --git a/options/gitignore/NotesAndExtendedConfiguration b/options/gitignore/NotesAndExtendedConfiguration new file mode 100644 index 0000000000..3e0804f299 --- /dev/null +++ b/options/gitignore/NotesAndExtendedConfiguration @@ -0,0 +1,38 @@ +# Excludes Obsidian workspace cache and plugin code, but retains plugin +# configuration. All notes and user-controlled configuration files are tracked +# by Git. +# +# !!! WARNING !!! +# +# Community plugins may store sensitive secrets in their data.json files. By +# including these files, those secrets may be tracked in your Git repository. +# +# To ignore configurations for specific plugins, add a line like this after the +# contents of this file (order is important): +# .obsidian/plugins/{{plugin_name}}/data.json +# +# Alternatively, ensure that you are treating your entire Git repository as +# sensitive data, since it may contain secrets, or may have contained them in +# past commits. Understand your threat profile, and make the decision +# appropriate for yourself. If in doubt, err on the side of not including +# plugin configuration. Use one of the alternative gitignore files instead: +# * NotesOnly.gitignore +# * NotesAndCoreConfiguration.gitignore + +# The current application UI state (DOM layout, recently-opened files, etc.) is +# stored in these files (separate for desktop and mobile) so you can resume +# your session seamlessly after a restart. If you want to track UI state, use +# the Workspaces core plugin instead of relying on these files. +.obsidian/workspace.json +.obsidian/workspace-mobile.json + +# Obsidian plugins are stored under .obsidian/plugins/$plugin_name. They +# contain metadata (manifest.json), application code (main.js), stylesheets +# (styles.css), and user-configuration data (data.json). +# We only want to track data.json, so we: +# 1. exclude everything under the plugins directory recursively, +# 2. unignore the plugin directories themselves, which then allows us to +# 3. unignore the data.json files +.obsidian/plugins/**/* +!.obsidian/plugins/*/ +!.obsidian/plugins/*/data.json diff --git a/options/gitignore/NotesOnly b/options/gitignore/NotesOnly new file mode 100644 index 0000000000..2b3b76ee0e --- /dev/null +++ b/options/gitignore/NotesOnly @@ -0,0 +1,4 @@ +# Excludes all Obsidian-related configuration. All notes are tracked by Git. + +# All Obsidian configuration and runtime state is stored here +.obsidian/**/* diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f053a429f9..03f3af7415 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1996,6 +1996,9 @@ pulls.reopen_failed.base_branch = The pull request cannot be reopened, because t pulls.made_using_agit = AGit pulls.agit_explanation = Created using the AGit workflow. AGit lets contributors propose changes using "git push" without creating a fork or a new branch. +pulls.editable = Editable +pulls.editable_explanation = This pull request allows edits from maintainers. You can contribute directly to it. + pulls.auto_merge_button_when_succeed = (When checks succeed) pulls.auto_merge_when_succeed = Auto merge when all checks succeed pulls.auto_merge_newly_scheduled = The pull request was scheduled to merge when all checks succeed. @@ -2888,8 +2891,8 @@ settings.update_settings = Update settings settings.update_setting_success = Organization settings have been updated. settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name. settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed. -settings.change_orgname_redirect_prompt.with_cooldown.one = The old username will be available to everyone after a cooldown period of %[1]d day, you can still reclaim the old username during the cooldown period. -settings.change_orgname_redirect_prompt.with_cooldown.few = The old username will be available to everyone after a cooldown period of %[1]d days, you can still reclaim the old username during the cooldown period. +settings.change_orgname_redirect_prompt.with_cooldown.one = The old organization name will be available to everyone after a cooldown period of %[1]d day, you can still reclaim the old name during the cooldown period. +settings.change_orgname_redirect_prompt.with_cooldown.few = The old organization name will be available to everyone after a cooldown period of %[1]d days, you can still reclaim the old name during the cooldown period. settings.update_avatar_success = The organization's avatar has been updated. settings.delete = Delete organization settings.delete_account = Delete this organization diff --git a/package-lock.json b/package-lock.json index 36dd2ff649..6cabd0476f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", - "postcss": "8.5.1", + "postcss": "8.5.2", "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "pretty-ms": "9.0.0", @@ -12643,9 +12643,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "funding": [ { "type": "opencollective", diff --git a/package.json b/package.json index ead8b90f2a..4fd28ae6dc 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", - "postcss": "8.5.1", + "postcss": "8.5.2", "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "pretty-ms": "9.0.0", diff --git a/routers/api/v1/activitypub/repository.go b/routers/api/v1/activitypub/repository.go index bc6e7905a6..14381664d4 100644 --- a/routers/api/v1/activitypub/repository.go +++ b/routers/api/v1/activitypub/repository.go @@ -70,8 +70,8 @@ func RepositoryInbox(ctx *context.APIContext) { repository := ctx.Repo.Repository log.Info("RepositoryInbox: repo: %v", repository) - form := web.GetForm(ctx) + // TODO: Decide between like/undo{like} activity httpStatus, title, err := federation.ProcessLikeActivity(ctx, form, repository.ID) if err != nil { ctx.Error(httpStatus, title, err) diff --git a/routers/common/middleware.go b/routers/common/middleware.go index ebc4d62d03..8d136ef2c7 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -95,10 +95,9 @@ func stripSlashesMiddleware(next http.Handler) http.Handler { prevWasSlash = chr == '/' } - if rctx == nil { - req.URL.Path = sanitizedPath.String() - } else { - rctx.RoutePath = sanitizedPath.String() + req.URL.Path = sanitizedPath.String() + if rctx != nil { + rctx.RoutePath = req.URL.Path } next.ServeHTTP(resp, req) }) diff --git a/routers/common/middleware_test.go b/routers/common/middleware_test.go index f16b9374ec..0e111e1261 100644 --- a/routers/common/middleware_test.go +++ b/routers/common/middleware_test.go @@ -7,6 +7,9 @@ import ( "net/http/httptest" "testing" + "code.gitea.io/gitea/modules/web" + + chi "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" ) @@ -43,6 +46,11 @@ func TestStripSlashesMiddleware(t *testing.T) { inputPath: "/user2//repo1/", expectedPath: "/user2/repo1", }, + { + name: "path with slashes in the beginning", + inputPath: "https://codeberg.org//user2/repo1/", + expectedPath: "/user2/repo1", + }, { name: "path with slashes and query params", inputPath: "/repo//migrate?service_type=3", @@ -56,15 +64,22 @@ func TestStripSlashesMiddleware(t *testing.T) { } for _, tt := range tests { - testMiddleware := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r := web.NewRoute() + r.Use(stripSlashesMiddleware) + + called := false + r.Get("*", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, tt.expectedPath, r.URL.Path) + + rctx := chi.RouteContext(r.Context()) + assert.Equal(t, tt.expectedPath, rctx.RoutePath) + + called = true }) - // pass the test middleware to validate the changes - handlerToTest := stripSlashesMiddleware(testMiddleware) // create a mock request to use req := httptest.NewRequest("GET", tt.inputPath, nil) - // call the handler using a mock response recorder - handlerToTest.ServeHTTP(httptest.NewRecorder(), req) + r.ServeHTTP(httptest.NewRecorder(), req) + assert.True(t, called) } } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 2feb898224..c2436a1c59 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -21,7 +21,6 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitgraph" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -32,6 +31,7 @@ import ( "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/gitdiff" git_service "code.gitea.io/gitea/services/repository" + "code.gitea.io/gitea/services/repository/gitgraph" ) const ( diff --git a/services/context/repo.go b/services/context/repo.go index ff03844c03..8b8359fb70 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -1058,7 +1058,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if refType == RepoRefLegacy { // redirect from old URL scheme to new URL scheme - prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink)) + prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.PathParamRaw("*"))), strings.ToLower(ctx.Repo.RepoLink)) ctx.Redirect(path.Join( ctx.Repo.RepoLink, diff --git a/modules/gitgraph/graph.go b/services/repository/gitgraph/graph.go similarity index 100% rename from modules/gitgraph/graph.go rename to services/repository/gitgraph/graph.go diff --git a/modules/gitgraph/graph_models.go b/services/repository/gitgraph/graph_models.go similarity index 100% rename from modules/gitgraph/graph_models.go rename to services/repository/gitgraph/graph_models.go diff --git a/modules/gitgraph/graph_test.go b/services/repository/gitgraph/graph_test.go similarity index 100% rename from modules/gitgraph/graph_test.go rename to services/repository/gitgraph/graph_test.go diff --git a/modules/gitgraph/parser.go b/services/repository/gitgraph/parser.go similarity index 100% rename from modules/gitgraph/parser.go rename to services/repository/gitgraph/parser.go diff --git a/templates/admin/auth/list.tmpl b/templates/admin/auth/list.tmpl index c162b7b74d..9c283fe3d8 100644 --- a/templates/admin/auth/list.tmpl +++ b/templates/admin/auth/list.tmpl @@ -30,6 +30,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-pencil"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index b07c6fcc01..8796794aee 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -66,6 +66,8 @@ + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 04a0b64432..4f8783dd42 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -24,6 +24,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-note" 16}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} {{if .Notices}} diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index d0805c85bc..b719d259e0 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -66,6 +66,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-pencil"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index aa38731d4f..f22600a449 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -74,6 +74,8 @@ {{DateUtils.AbsoluteShort .Version.CreatedUnix}} {{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index c4924c3fac..0422705ea9 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -86,6 +86,8 @@ {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index 9b3447f44a..f4609edbbf 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -109,6 +109,8 @@ + {{else}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 936df9d3d2..6f65807b88 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -95,6 +95,11 @@ {{end}} + {{if and .Issue.PullRequest.AllowMaintainerEdit .CanWriteCode}} + + {{ctx.Locale.Tr "repo.pulls.editable"}} + + {{end}}