diff --git a/cmd/soft/root.go b/cmd/soft/root.go index edd7c2149..1dc8e1883 100644 --- a/cmd/soft/root.go +++ b/cmd/soft/root.go @@ -4,11 +4,9 @@ import ( "context" "os" "runtime/debug" - "strconv" - "strings" - "time" "github.com/charmbracelet/log" + . "github.com/charmbracelet/soft-serve/internal/log" "github.com/spf13/cobra" ) @@ -53,28 +51,8 @@ func init() { } func main() { - ctx := context.Background() - logger := log.NewWithOptions(os.Stderr, log.Options{ - ReportTimestamp: true, - TimeFormat: time.DateOnly, - }) - if debug, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_DEBUG")); debug { - logger.SetLevel(log.DebugLevel) - } - if tsfmt := os.Getenv("SOFT_SERVE_LOG_TIME_FORMAT"); tsfmt != "" { - logger.SetTimeFormat(tsfmt) - } - - switch strings.ToLower(os.Getenv("SOFT_SERVE_LOG_FORMAT")) { - case "json": - logger.SetFormatter(log.JSONFormatter) - case "logfmt": - logger.SetFormatter(log.LogfmtFormatter) - case "text": - logger.SetFormatter(log.TextFormatter) - } - - ctx = log.WithContext(ctx, logger) + logger := NewDefaultLogger() + ctx := log.WithContext(context.Background(), logger) if err := rootCmd.ExecuteContext(ctx); err != nil { os.Exit(1) } diff --git a/git/repo.go b/git/repo.go index 050d3f67f..67a0e286d 100644 --- a/git/repo.go +++ b/git/repo.go @@ -147,7 +147,11 @@ func (r *Repository) TreePath(ref *Reference, path string) (*Tree, error) { // Diff returns the diff for the given commit. func (r *Repository) Diff(commit *Commit) (*Diff, error) { - ddiff, err := r.Repository.Diff(commit.Hash.String(), DiffMaxFiles, DiffMaxFileLines, DiffMaxLineChars) + ddiff, err := r.Repository.Diff(commit.Hash.String(), DiffMaxFiles, DiffMaxFileLines, DiffMaxLineChars, git.DiffOptions{ + CommandOptions: git.CommandOptions{ + Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"}, + }, + }) if err != nil { return nil, err } diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 000000000..a1184153b --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,39 @@ +package log + +import ( + "os" + "strconv" + "strings" + "time" + + "github.com/charmbracelet/log" +) + +var contextKey = &struct{ string }{"logger"} + +// NewDefaultLogger returns a new logger with default settings. +func NewDefaultLogger() *log.Logger { + logger := log.NewWithOptions(os.Stderr, log.Options{ + ReportTimestamp: true, + TimeFormat: time.DateOnly, + }) + + if debug, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_DEBUG")); debug { + logger.SetLevel(log.DebugLevel) + } + + if tsfmt := os.Getenv("SOFT_SERVE_LOG_TIME_FORMAT"); tsfmt != "" { + logger.SetTimeFormat(tsfmt) + } + + switch strings.ToLower(os.Getenv("SOFT_SERVE_LOG_FORMAT")) { + case "json": + logger.SetFormatter(log.JSONFormatter) + case "logfmt": + logger.SetFormatter(log.LogfmtFormatter) + case "text": + logger.SetFormatter(log.TextFormatter) + } + + return logger +} diff --git a/server/backend/sqlite/hooks.go b/server/backend/sqlite/hooks.go index b3691b187..ff39046f4 100644 --- a/server/backend/sqlite/hooks.go +++ b/server/backend/sqlite/hooks.go @@ -75,9 +75,12 @@ func updateServerInfo(d *SqliteBackend, repo string) error { func populateLastModified(d *SqliteBackend, repo string) error { var rr *Repo - if rr, err := d.Repository(repo); err != nil { + _rr, err := d.Repository(repo) + if err != nil { return err - } else if r, ok := rr.(*Repo); ok { + } + + if r, ok := _rr.(*Repo); ok { rr = r } else { return ErrRepoNotExist diff --git a/server/backend/sqlite/sqlite.go b/server/backend/sqlite/sqlite.go index e4c911132..91527e3fc 100644 --- a/server/backend/sqlite/sqlite.go +++ b/server/backend/sqlite/sqlite.go @@ -144,28 +144,25 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt repo := name + ".git" rp := filepath.Join(d.reposPath(), repo) - cleanup := func() error { - return os.RemoveAll(rp) - } + if err := wrapTx(d.db, d.ctx, func(tx *sqlx.Tx) error { + if _, err := tx.Exec(`INSERT INTO repo (name, project_name, description, private, mirror, hidden, updated_at) + VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`, + name, opts.ProjectName, opts.Description, opts.Private, opts.Mirror, opts.Hidden); err != nil { + return err + } - rr, err := git.Init(rp, true) - if err != nil { - d.logger.Debug("failed to create repository", "err", err) - cleanup() // nolint: errcheck - return nil, err - } + rr, err := git.Init(rp, true) + if err != nil { + d.logger.Debug("failed to create repository", "err", err) + return err + } - if err := rr.UpdateServerInfo(); err != nil { - d.logger.Debug("failed to update server info", "err", err) - cleanup() // nolint: errcheck - return nil, err - } + if err := rr.UpdateServerInfo(); err != nil { + d.logger.Debug("failed to update server info", "err", err) + return err + } - if err := wrapTx(d.db, d.ctx, func(tx *sqlx.Tx) error { - _, err := tx.Exec(`INSERT INTO repo (name, project_name, description, private, mirror, hidden, updated_at) - VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`, - name, opts.ProjectName, opts.Description, opts.Private, opts.Mirror, opts.Hidden) - return err + return nil }); err != nil { d.logger.Debug("failed to create repository in database", "err", err) return nil, wrapDbErr(err) @@ -216,6 +213,7 @@ func (d *SqliteBackend) ImportRepository(name string, remote string, opts backen if err := git.Clone(remote, rp, copts); err != nil { d.logger.Error("failed to clone repository", "err", err, "mirror", opts.Mirror, "remote", remote, "path", rp) + // Cleanup the mess! if rerr := os.RemoveAll(rp); rerr != nil { err = errors.Join(err, rerr) } @@ -237,11 +235,11 @@ func (d *SqliteBackend) DeleteRepository(name string) error { // Delete repo from cache defer d.cache.Delete(name) - if err := os.RemoveAll(rp); err != nil { + if _, err := tx.Exec("DELETE FROM repo WHERE name = ?;", name); err != nil { return err } - _, err := tx.Exec("DELETE FROM repo WHERE name = ?;", name) - return err + + return os.RemoveAll(rp) }) } @@ -263,32 +261,37 @@ func (d *SqliteBackend) RenameRepository(oldName string, newName string) error { op := filepath.Join(d.reposPath(), oldRepo) np := filepath.Join(d.reposPath(), newRepo) if _, err := os.Stat(op); err != nil { - return fmt.Errorf("repository %s does not exist", oldName) + return ErrRepoNotExist } if _, err := os.Stat(np); err == nil { - return fmt.Errorf("repository %s already exists", newName) + return ErrRepoExist } if err := wrapTx(d.db, d.ctx, func(tx *sqlx.Tx) error { + // Delete cache + defer d.cache.Delete(oldName) + _, err := tx.Exec("UPDATE repo SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?;", newName, oldName) - return err + if err != nil { + return err + } + + // Make sure the new repository parent directory exists. + if err := os.MkdirAll(filepath.Dir(np), os.ModePerm); err != nil { + return err + } + + if err := os.Rename(op, np); err != nil { + return err + } + + return nil }); err != nil { return wrapDbErr(err) } - // Make sure the new repository parent directory exists. - if err := os.MkdirAll(filepath.Dir(np), os.ModePerm); err != nil { - return err - } - - // Delete cache - d.cache.Delete(oldName) - defer func() { - d.Repository(newName) // nolint: errcheck - }() - - return os.Rename(op, np) + return nil } // Repositories returns a list of all repositories. diff --git a/server/git/git.go b/server/git/git.go index 0b14f0b50..85e2cf068 100644 --- a/server/git/git.go +++ b/server/git/git.go @@ -88,6 +88,7 @@ func RunGit(ctx context.Context, in io.Reader, out io.Writer, er io.Writer, dir c.Env = append(c.Env, "SOFT_SERVE_DEBUG="+os.Getenv("SOFT_SERVE_DEBUG")) if cfg != nil { c.Env = append(c.Env, "SOFT_SERVE_LOG_FORMAT="+cfg.LogFormat) + c.Env = append(c.Env, "SOFT_SERVE_LOG_TIME_FORMAT="+cfg.LogTimeFormat) } stdin, err := c.StdinPipe() diff --git a/server/ssh/session.go b/server/ssh/session.go index e24bdc83a..b39e15e24 100644 --- a/server/ssh/session.go +++ b/server/ssh/session.go @@ -4,6 +4,8 @@ import ( "strings" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/log" + . "github.com/charmbracelet/soft-serve/internal/log" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/config" "github.com/charmbracelet/soft-serve/server/errors" @@ -48,7 +50,9 @@ func SessionHandler(cfg *config.Config) bm.ProgramHandler { envs := &sessionEnv{s} output := termenv.NewOutput(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs)) - c := common.NewCommon(s.Context(), output, pty.Window.Width, pty.Window.Height) + logger := NewDefaultLogger() + ctx := log.WithContext(s.Context(), logger) + c := common.NewCommon(ctx, output, pty.Window.Width, pty.Window.Height) c.SetValue(common.ConfigKey, cfg) m := ui.New(c, initialRepo) p := tea.NewProgram(m, diff --git a/server/ui/components/code/code.go b/server/ui/components/code/code.go index bf3fc4c4d..829118334 100644 --- a/server/ui/components/code/code.go +++ b/server/ui/components/code/code.go @@ -6,7 +6,6 @@ import ( "sync" "github.com/alecthomas/chroma/lexers" - "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" gansi "github.com/charmbracelet/glamour/ansi" @@ -99,18 +98,10 @@ func (r *Code) Init() tea.Cmd { // Update implements tea.Model. func (r *Code) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds := make([]tea.Cmd, 0) - switch msg := msg.(type) { + switch msg.(type) { case tea.WindowSizeMsg: // Recalculate content width and line wrap. cmds = append(cmds, r.Init()) - case tea.KeyMsg: - // Viewport doesn't handle these keys, so we do it here. - switch { - case key.Matches(msg, r.common.KeyMap.GotoTop): - r.GotoTop() - case key.Matches(msg, r.common.KeyMap.GotoBottom): - r.GotoBottom() - } } v, cmd := r.Viewport.Update(msg) r.Viewport = v.(*vp.Viewport) diff --git a/server/ui/components/viewport/viewport.go b/server/ui/components/viewport/viewport.go index bdccc0f0a..35fe8f24d 100644 --- a/server/ui/components/viewport/viewport.go +++ b/server/ui/components/viewport/viewport.go @@ -1,6 +1,7 @@ package viewport import ( + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/soft-serve/server/ui/common" @@ -36,6 +37,15 @@ func (v *Viewport) Init() tea.Cmd { // Update implements tea.Model. func (v *Viewport) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, v.common.KeyMap.GotoTop): + v.GotoTop() + case key.Matches(msg, v.common.KeyMap.GotoBottom): + v.GotoBottom() + } + } vp, cmd := v.Model.Update(msg) v.Model = &vp return v, cmd diff --git a/server/ui/pages/repo/log.go b/server/ui/pages/repo/log.go index 0b1480b46..21bbcdf9f 100644 --- a/server/ui/pages/repo/log.go +++ b/server/ui/pages/repo/log.go @@ -105,6 +105,8 @@ func (l *Log) ShortHelp() []key.Binding { return []key.Binding{ l.common.KeyMap.UpDown, l.common.KeyMap.BackItem, + l.common.KeyMap.GotoTop, + l.common.KeyMap.GotoBottom, } default: return []key.Binding{} @@ -151,6 +153,8 @@ func (l *Log) FullHelp() [][]key.Binding { { k.Down, k.Up, + l.common.KeyMap.GotoTop, + l.common.KeyMap.GotoBottom, }, }...) } diff --git a/server/ui/pages/selection/item.go b/server/ui/pages/selection/item.go index bc0621073..6550497c7 100644 --- a/server/ui/pages/selection/item.go +++ b/server/ui/pages/selection/item.go @@ -83,7 +83,7 @@ func (i Item) Title() string { } // Description returns the item description. Implements list.DefaultItem. -func (i Item) Description() string { return i.repo.Description() } +func (i Item) Description() string { return strings.TrimSpace(i.repo.Description()) } // FilterValue implements list.Item. func (i Item) FilterValue() string { return i.Title() } @@ -199,13 +199,16 @@ func (d *ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem lis s.WriteRune('\n') s.WriteString(desc) s.WriteRune('\n') - cmd := common.TruncateString(i.Command(), m.Width()-styles.Base.GetHorizontalFrameSize()) - cmd = styles.Command.Render(cmd) + + cmd := i.Command() + cmdStyler := styles.Command.Render if d.copiedIdx == index { - cmd += " " + styles.Desc.Render("(copied to clipboard)") + cmd = "(copied to clipboard)" + cmdStyler = styles.Desc.Render d.copiedIdx = -1 } - s.WriteString(cmd) + cmd = common.TruncateString(cmd, m.Width()-styles.Base.GetHorizontalFrameSize()) + s.WriteString(cmdStyler(cmd)) fmt.Fprint(w, d.common.Zone.Mark(i.ID(), styles.Base.Render(s.String()),