Detailed changes
@@ -501,8 +501,8 @@ Available Commands:
hide Hide or unhide a repository
import Import a new repository from remote
info Get information about a repository
- is-mirror Whether a repository is a mirror
list List repositories
+ mirror Set or get a repository mirror property
private Set or get a repository private property
project-name Set or get the project name for a repository
rename Rename an existing repository
@@ -553,6 +553,69 @@ func (d *Backend) SetHidden(ctx context.Context, name string, hidden bool) error
}))
}
+// SetMirror sets the mirror flag of a repository.
+// Note: enabling mirror mode requires the repository to have been imported
+// with a remote URL. Use ImportRepository to create a new mirror.
+func (d *Backend) SetMirror(ctx context.Context, name string, mirror bool) error {
+ name = utils.SanitizeRepo(name)
+ rp := filepath.Join(d.repoPath(name))
+
+ // Delete cache
+ d.cache.Delete(name)
+
+ return db.WrapError(d.db.TransactionContext(ctx, func(tx *db.Tx) error {
+ // Update git config
+ r, err := git.Open(rp)
+ if err != nil {
+ return err
+ }
+
+ rcfg, err := r.Config()
+ if err != nil {
+ return err
+ }
+
+ // Update mirror option for all remotes
+ remoteSection := rcfg.Section("remote")
+ hasRemote := false
+ for _, sub := range remoteSection.Subsections {
+ // Check if this remote has a URL
+ for _, opt := range sub.Options {
+ if opt.Key == "url" && opt.Value != "" {
+ hasRemote = true
+ found := false
+ for i, opt := range sub.Options {
+ if opt.Key == "mirror" {
+ found = true
+ if mirror {
+ sub.Options[i].Value = "true"
+ } else {
+ sub.Options = append(sub.Options[:i], sub.Options[i+1:]...)
+ }
+ break
+ }
+ }
+ if !found && mirror {
+ sub.SetOption("mirror", "true")
+ }
+ break
+ }
+ }
+ }
+
+ if mirror && !hasRemote {
+ return errors.New("cannot enable mirror mode: repository has no remote URL configured")
+ }
+
+ if err := r.SetConfig(rcfg); err != nil {
+ d.logger.Error("failed to set repository config", "err", err, "path", rp)
+ return err
+ }
+
+ return d.store.SetRepoIsMirrorByName(ctx, tx, name, mirror)
+ }))
+}
+
// SetDescription sets the description of a repository.
//
// It implements backend.Backend.
@@ -7,21 +7,34 @@ import (
func mirrorCommand() *cobra.Command {
cmd := &cobra.Command{
- Use: "is-mirror REPOSITORY",
- Short: "Whether a repository is a mirror",
- Args: cobra.ExactArgs(1),
+ Use: "mirror REPOSITORY [true|false]",
+ Short: "Set or get a repository mirror property",
+ Args: cobra.RangeArgs(1, 2),
PersistentPreRunE: checkIfReadable,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
be := backend.FromContext(ctx)
rn := args[0]
- rr, err := be.Repository(ctx, rn)
- if err != nil {
- return err
+
+ switch len(args) {
+ case 1:
+ isMirror, err := be.IsMirror(ctx, rn)
+ if err != nil {
+ return err
+ }
+
+ cmd.Println(isMirror)
+ case 2:
+ if err := checkIfCollab(cmd, args); err != nil {
+ return err
+ }
+
+ isMirror := args[1] == "true"
+ if err := be.SetMirror(ctx, rn, isMirror); err != nil {
+ return err
+ }
}
- isMirror := rr.IsMirror()
- cmd.Println(isMirror)
return nil
},
}
@@ -92,6 +92,14 @@ func (*repoStore) GetRepoIsMirrorByName(ctx context.Context, tx db.Handler, name
return isMirror, db.WrapError(err)
}
+// SetRepoIsMirrorByName implements store.RepositoryStore.
+func (*repoStore) SetRepoIsMirrorByName(ctx context.Context, tx db.Handler, name string, isMirror bool) error {
+ name = utils.SanitizeRepo(name)
+ query := tx.Rebind("UPDATE repos SET mirror = ? WHERE name = ?;")
+ _, err := tx.ExecContext(ctx, query, isMirror, name)
+ return db.WrapError(err)
+}
+
// GetRepoIsPrivateByName implements store.RepositoryStore.
func (*repoStore) GetRepoIsPrivateByName(ctx context.Context, tx db.Handler, name string) (bool, error) {
var isPrivate bool
@@ -25,4 +25,5 @@ type RepositoryStore interface {
GetRepoIsHiddenByName(ctx context.Context, h db.Handler, name string) (bool, error)
SetRepoIsHiddenByName(ctx context.Context, h db.Handler, name string, isHidden bool) error
GetRepoIsMirrorByName(ctx context.Context, h db.Handler, name string) (bool, error)
+ SetRepoIsMirrorByName(ctx context.Context, h db.Handler, name string, isMirror bool) error
}
@@ -22,9 +22,28 @@ cmp stdout info1.txt
soft repo list
stdout charmbracelet/wizard-tutorial
-# is-mirror?
-soft repo is-mirror charmbracelet/wizard-tutorial
+# create a non-mirror repo
+soft repo create test-normal
+soft repo mirror test-normal
+stdout false
+# try to enable mirror on repo without remote - should fail
+! soft repo mirror test-normal true
+
+# disable mirror
+soft repo mirror charmbracelet/wizard-tutorial false
+soft repo mirror charmbracelet/wizard-tutorial
+stdout false
+# verify git config no longer has mirror
+readfile $DATA_PATH/repos/charmbracelet/wizard-tutorial.git/config
+! stdout 'mirror = true'
+
+# re-enable mirror
+soft repo mirror charmbracelet/wizard-tutorial true
+soft repo mirror charmbracelet/wizard-tutorial
stdout true
+# verify git config has mirror again
+readfile $DATA_PATH/repos/charmbracelet/wizard-tutorial.git/config
+stdout 'mirror = true'
# set project name
soft repo project-name charmbracelet/wizard-tutorial wizard-tutorial