Diff
1diff --git a/server/backend/file/file.go b/server/backend/file/file.go
2index 9f855db570573db36b0b313e8ce6addf49d877a6..dfb80526172c31df5976ab7838b0098261765e04 100644
3--- a/server/backend/file/file.go
4+++ b/server/backend/file/file.go
5@@ -49,6 +49,7 @@ const (
6 description = "description"
7 exportOk = "git-daemon-export-ok"
8 private = "private"
9+ projectName = "project-name"
10 settings = "settings"
11 )
12
13@@ -573,6 +574,23 @@ func (fb *FileBackend) SetPrivate(repo string, priv bool) error {
14 return nil
15 }
16
17+// ProjectName returns the project name.
18+//
19+// It implements backend.Backend.
20+func (fb *FileBackend) ProjectName(repo string) string {
21+ repo = utils.SanitizeRepo(repo) + ".git"
22+ r := &Repo{path: filepath.Join(fb.reposPath(), repo), root: fb.reposPath()}
23+ return r.ProjectName()
24+}
25+
26+// SetProjectName sets the project name of the given repo.
27+//
28+// It implements backend.Backend.
29+func (fb *FileBackend) SetProjectName(repo string, name string) error {
30+ repo = utils.SanitizeRepo(repo) + ".git"
31+ return os.WriteFile(filepath.Join(fb.reposPath(), repo, projectName), []byte(name), 0600)
32+}
33+
34 // CreateRepository creates a new repository.
35 //
36 // Created repositories are always bare.
37@@ -607,6 +625,11 @@ func (fb *FileBackend) CreateRepository(repo string, private bool) (backend.Repo
38 return nil, err
39 }
40
41+ if err := fb.SetProjectName(repo, name); err != nil {
42+ logger.Debug("failed to set project name", "err", err)
43+ return nil, err
44+ }
45+
46 r := &Repo{path: rp, root: fb.reposPath()}
47 // Add to cache.
48 fb.repos[name] = r
49diff --git a/server/backend/file/repo.go b/server/backend/file/repo.go
50index 6e760d4295ea089d1951192ca369c1484e70038c..3888201045f5a16b39f85a373b2adfec9a46a452 100644
51--- a/server/backend/file/repo.go
52+++ b/server/backend/file/repo.go
53@@ -27,14 +27,22 @@ func (r *Repo) Name() string {
54 return strings.TrimPrefix(name, "/")
55 }
56
57+// ProjectName returns the repository's project name.
58+func (r *Repo) ProjectName() string {
59+ pn, err := readOneLine(filepath.Join(r.path, projectName))
60+ if err != nil {
61+ return ""
62+ }
63+
64+ return strings.TrimSpace(pn)
65+}
66+
67 // Description returns the repository's description.
68 //
69 // It implements backend.Repository.
70 func (r *Repo) Description() string {
71 desc, err := readAll(filepath.Join(r.path, description))
72 if err != nil {
73- logger.Debug("failed to read description file", "err", err,
74- "path", filepath.Join(r.path, description))
75 return ""
76 }
77
78diff --git a/server/backend/repo.go b/server/backend/repo.go
79index 05d2de240656cca7749dcec0afb5bdd5d4bfd8e8..9fcad1c45516d21201e65021877635f82c6320b8 100644
80--- a/server/backend/repo.go
81+++ b/server/backend/repo.go
82@@ -21,6 +21,10 @@ type RepositoryStore interface {
83
84 // RepositoryMetadata is an interface for managing repository metadata.
85 type RepositoryMetadata interface {
86+ // ProjectName returns the repository's project name.
87+ ProjectName(repo string) string
88+ // SetProjectName sets the repository's project name.
89+ SetProjectName(repo, name string) error
90 // Description returns the repository's description.
91 Description(repo string) string
92 // SetDescription sets the repository's description.
93@@ -57,6 +61,8 @@ type RepositoryAccess interface {
94 type Repository interface {
95 // Name returns the repository's name.
96 Name() string
97+ // ProjectName returns the repository's project name.
98+ ProjectName() string
99 // Description returns the repository's description.
100 Description() string
101 // IsPrivate returns whether the repository is private.
102diff --git a/server/cmd/cmd.go b/server/cmd/cmd.go
103index 461a9041f126e3526dfb0fed2f5f7bce7cc0d914..f6fcdb13164b478c9488d1f9b46a5104bacd43bf 100644
104--- a/server/cmd/cmd.go
105+++ b/server/cmd/cmd.go
106@@ -64,6 +64,7 @@ func rootCommand() *cobra.Command {
107 hookCommand(),
108 listCommand(),
109 privateCommand(),
110+ projectName(),
111 renameCommand(),
112 settingCommand(),
113 tagCommand(),
114diff --git a/server/cmd/project_name.go b/server/cmd/project_name.go
115new file mode 100644
116index 0000000000000000000000000000000000000000..68ca47c50bd0e06f61b67d4d2b64e330f7ce2e97
117--- /dev/null
118+++ b/server/cmd/project_name.go
119@@ -0,0 +1,40 @@
120+package cmd
121+
122+import (
123+ "strings"
124+
125+ "github.com/spf13/cobra"
126+)
127+
128+func projectName() *cobra.Command {
129+ cmd := &cobra.Command{
130+ Use: "project-name REPOSITORY [NAME]",
131+ Aliases: []string{"project"},
132+ Short: "Set or get the project name for a repository",
133+ Args: cobra.MinimumNArgs(1),
134+ RunE: func(cmd *cobra.Command, args []string) error {
135+ cfg, _ := fromContext(cmd)
136+ rn := strings.TrimSuffix(args[0], ".git")
137+ switch len(args) {
138+ case 1:
139+ if err := checkIfReadable(cmd, args); err != nil {
140+ return err
141+ }
142+
143+ pn := cfg.Backend.ProjectName(rn)
144+ cmd.Println(pn)
145+ default:
146+ if err := checkIfCollab(cmd, args); err != nil {
147+ return err
148+ }
149+ if err := cfg.Backend.SetProjectName(rn, strings.Join(args[1:], " ")); err != nil {
150+ return err
151+ }
152+ }
153+
154+ return nil
155+ },
156+ }
157+
158+ return cmd
159+}
160diff --git a/ui/pages/repo/repo.go b/ui/pages/repo/repo.go
161index 75f3ac38ad302cc3af82ef9633141fe11479ffbb..ff94c890d955ada5b2fd679619d5c0764db72c0a 100644
162--- a/ui/pages/repo/repo.go
163+++ b/ui/pages/repo/repo.go
164@@ -327,7 +327,11 @@ func (r *Repo) headerView() string {
165 return ""
166 }
167 truncate := lipgloss.NewStyle().MaxWidth(r.common.Width)
168- name := r.common.Styles.Repo.HeaderName.Render(r.selectedRepo.Name())
169+ name := r.selectedRepo.ProjectName()
170+ if name == "" {
171+ name = r.selectedRepo.Name()
172+ }
173+ name = r.common.Styles.Repo.HeaderName.Render(name)
174 desc := r.selectedRepo.Description()
175 if desc == "" {
176 desc = name
177diff --git a/ui/pages/selection/item.go b/ui/pages/selection/item.go
178index 83bf3e6c3cdb17f60d1580de113059ea1c15f078..79c5b634622e9f5d016eb7175d4f11639cf4323e 100644
179--- a/ui/pages/selection/item.go
180+++ b/ui/pages/selection/item.go
181@@ -79,7 +79,12 @@ func (i Item) ID() string {
182
183 // Title returns the item title. Implements list.DefaultItem.
184 func (i Item) Title() string {
185- return i.repo.Name()
186+ name := i.repo.ProjectName()
187+ if name == "" {
188+ name = i.repo.Name()
189+ }
190+
191+ return name
192 }
193
194 // Description returns the item description. Implements list.DefaultItem.