git: fix a bunch of small bugs.

This commit is contained in:
fiatjaf
2025-11-30 08:57:27 -03:00
parent f9335b0ab4
commit 210cf66d5f

209
git.go
View File

@@ -93,6 +93,9 @@ aside from those, there is also:
} }
} }
var defaultOwner string
var defaultIdentifier string
// check if nip34.json already exists // check if nip34.json already exists
existingConfig, err := readNip34ConfigFile("") existingConfig, err := readNip34ConfigFile("")
if err == nil { if err == nil {
@@ -100,22 +103,36 @@ aside from those, there is also:
if !c.Bool("force") && !c.Bool("interactive") { if !c.Bool("force") && !c.Bool("interactive") {
return fmt.Errorf("nip34.json already exists, use --force to overwrite or --interactive to update") return fmt.Errorf("nip34.json already exists, use --force to overwrite or --interactive to update")
} }
defaultIdentifier = existingConfig.Identifier
defaultOwner = existingConfig.Owner
} else {
// extract info from nostr:// git remotes (this is just for migrating from ngit)
if output, err := exec.Command("git", "remote", "-v").Output(); err == nil {
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
for _, remote := range remotes {
if strings.Contains(remote, "nostr://") {
parts := strings.Fields(remote)
if len(parts) >= 2 {
nostrURL := parts[1]
// parse nostr://npub.../relay_hostname/identifier
if remoteOwner, remoteIdentifier, relays, err := parseRepositoryAddress(ctx, nostrURL); err == nil && len(relays) > 0 {
defaultIdentifier = remoteIdentifier
defaultOwner = nip19.EncodeNpub(remoteOwner)
}
}
}
}
}
} }
// get repository base directory name for defaults // get repository base directory name for defaults
cwd, err := os.Getwd() if defaultIdentifier == "" {
if err != nil { cwd, err := os.Getwd()
return fmt.Errorf("failed to get current directory: %w", err) if err != nil {
} return fmt.Errorf("failed to get current directory: %w", err)
baseName := filepath.Base(cwd)
// get earliest unique commit
var earliestCommit string
if output, err := exec.Command("git", "rev-list", "--max-parents=0", "HEAD").Output(); err == nil {
earliest := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(earliest) > 0 {
earliestCommit = earliest[0]
} }
defaultIdentifier = filepath.Base(cwd)
} }
// prompt for identifier first // prompt for identifier first
@@ -123,15 +140,14 @@ aside from those, there is also:
if c.String("identifier") != "" { if c.String("identifier") != "" {
identifier = c.String("identifier") identifier = c.String("identifier")
} else if c.Bool("interactive") { } else if c.Bool("interactive") {
identifierPrompt := &survey.Input{ if err := survey.AskOne(&survey.Input{
Message: "identifier", Message: "identifier",
Default: baseName, Default: defaultIdentifier,
} }, &identifier); err != nil {
if err := survey.AskOne(identifierPrompt, &identifier); err != nil {
return err return err
} }
} else { } else {
identifier = baseName identifier = defaultIdentifier
} }
// prompt for owner pubkey // prompt for owner pubkey
@@ -145,10 +161,10 @@ aside from those, there is also:
ownerStr = nip19.EncodeNpub(owner) ownerStr = nip19.EncodeNpub(owner)
} else if c.Bool("interactive") { } else if c.Bool("interactive") {
for { for {
ownerPrompt := &survey.Input{ if err := survey.AskOne(&survey.Input{
Message: "owner (npub or hex)", Message: "owner (npub or hex)",
} Default: defaultOwner,
if err := survey.AskOne(ownerPrompt, &ownerStr); err != nil { }, &ownerStr); err != nil {
return err return err
} }
owner, err = parsePubKey(ownerStr) owner, err = parsePubKey(ownerStr)
@@ -162,36 +178,40 @@ aside from those, there is also:
} }
// try to fetch existing repository announcement (kind 30617) // try to fetch existing repository announcement (kind 30617)
log(" searching for existing events... ")
repo, _, err := fetchRepositoryAndState(ctx, owner, identifier, nil)
var fetchedRepo *nip34.Repository var fetchedRepo *nip34.Repository
if err == nil && repo.Event.ID != nostr.ZeroID { if existingConfig.Identifier == "" {
fetchedRepo = &repo log(" searching for existing events... ")
log("found one from %s.\n", repo.Event.CreatedAt.Time().Format(time.DateOnly)) repo, _, err := fetchRepositoryAndState(ctx, owner, identifier, nil)
} else { if err == nil && repo.Event.ID != nostr.ZeroID {
log("none found.\n") fetchedRepo = &repo
log("found one from %s.\n", repo.Event.CreatedAt.Time().Format(time.DateOnly))
} else {
log("none found.\n")
}
} }
// extract clone URLs from nostr:// git remotes // set config with fetched values or defaults
// (this is just for migrating from ngit) var config Nip34Config
var defaultCloneURLs []string if fetchedRepo != nil {
if output, err := exec.Command("git", "remote", "-v").Output(); err == nil { config = RepositoryToConfig(*fetchedRepo)
remotes := strings.Split(strings.TrimSpace(string(output)), "\n") } else if existingConfig.Identifier != "" {
for _, remote := range remotes { config = existingConfig
if strings.Contains(remote, "nostr://") { } else {
parts := strings.Fields(remote) // get earliest unique commit
if len(parts) >= 2 { var earliestCommit string
nostrURL := parts[1] if output, err := exec.Command("git", "rev-list", "--max-parents=0", "HEAD").Output(); err == nil {
// parse nostr://npub.../relay_hostname/identifier earliestCommit = strings.TrimSpace(string(output))
if remoteOwner, remoteIdentifier, relays, err := parseRepositoryAddress(ctx, nostrURL); err == nil && len(relays) > 0 { }
relayURL := relays[0]
// convert to https://relay_hostname/npub.../identifier.git config = Nip34Config{
cloneURL := fmt.Sprintf("http%s/%s/%s.git", Identifier: identifier,
relayURL[2:], nip19.EncodeNpub(remoteOwner), remoteIdentifier) Owner: ownerStr,
defaultCloneURLs = appendUnique(defaultCloneURLs, cloneURL) Name: identifier,
} Description: "",
} Web: []string{},
} GraspServers: []string{"gitnostr.com", "relay.ngit.dev"},
EarliestUniqueCommit: earliestCommit,
Maintainers: []string{},
} }
} }
@@ -216,23 +236,6 @@ aside from those, there is also:
return defaultVals return defaultVals
} }
// set config with fetched values or defaults
var config Nip34Config
if fetchedRepo != nil {
config = RepositoryToConfig(*fetchedRepo)
} else {
config = Nip34Config{
Identifier: identifier,
Owner: ownerStr,
Name: baseName,
Description: "",
Web: []string{},
GraspServers: []string{"gitnostr.com", "relay.ngit.dev"},
EarliestUniqueCommit: earliestCommit,
Maintainers: []string{},
}
}
// override with flags and existing config // override with flags and existing config
config.Identifier = getValue(existingConfig.Identifier, c.String("identifier"), config.Identifier) config.Identifier = getValue(existingConfig.Identifier, c.String("identifier"), config.Identifier)
config.Name = getValue(existingConfig.Name, c.String("name"), config.Name) config.Name = getValue(existingConfig.Name, c.String("name"), config.Name)
@@ -245,20 +248,18 @@ aside from those, there is also:
if c.Bool("interactive") { if c.Bool("interactive") {
// prompt for name // prompt for name
namePrompt := &survey.Input{ if err := survey.AskOne(&survey.Input{
Message: "name", Message: "name",
Default: config.Name, Default: config.Name,
} }, &config.Name); err != nil {
if err := survey.AskOne(namePrompt, &config.Name); err != nil {
return err return err
} }
// prompt for description // prompt for description
descPrompt := &survey.Input{ if err := survey.AskOne(&survey.Input{
Message: "description", Message: "description",
Default: config.Description, Default: config.Description,
} }, &config.Description); err != nil {
if err := survey.AskOne(descPrompt, &config.Description); err != nil {
return err return err
} }
@@ -288,6 +289,14 @@ aside from those, there is also:
} }
config.Web = webURLs config.Web = webURLs
// prompt for earliest unique commit
if err := survey.AskOne(&survey.Input{
Message: "earliest unique commit",
Default: config.EarliestUniqueCommit,
}, &config.EarliestUniqueCommit); err != nil {
return err
}
// Prompt for maintainers // Prompt for maintainers
maintainers, err := promptForStringList("maintainers", config.Maintainers, []string{}, nil, func(s string) bool { maintainers, err := promptForStringList("maintainers", config.Maintainers, []string{}, nil, func(s string) bool {
pk, err := parsePubKey(s) pk, err := parsePubKey(s)
@@ -537,8 +546,7 @@ aside from those, there is also:
pushSuccesses := 0 pushSuccesses := 0
for _, relay := range repo.Relays { for _, relay := range repo.Relays {
relayURL := nostr.NormalizeURL(relay) relayURL := nostr.NormalizeURL(relay)
remoteName := "nip34/grasp/" + graspServerHost(relayURL) remoteName := gitRemoteName(relayURL)
remoteName = strings.TrimPrefix(remoteName, "ws://")
log("pushing to %s...\n", color.CyanString(remoteName)) log("pushing to %s...\n", color.CyanString(remoteName))
pushArgs := []string{"push", remoteName, fmt.Sprintf("%s:refs/heads/%s", localBranch, remoteBranch)} pushArgs := []string{"push", remoteName, fmt.Sprintf("%s:refs/heads/%s", localBranch, remoteBranch)}
@@ -731,16 +739,16 @@ aside from those, there is also:
func promptForStringList( func promptForStringList(
name string, name string,
existing []string,
defaults []string, defaults []string,
alternatives []string,
normalize func(string) string, normalize func(string) string,
validate func(string) bool, validate func(string) bool,
) ([]string, error) { ) ([]string, error) {
options := make([]string, 0, len(defaults)+len(existing)+1) options := make([]string, 0, len(defaults)+len(alternatives)+1)
options = append(options, defaults...) options = append(options, defaults...)
// add existing not in options // add existing not in options
for _, item := range existing { for _, item := range alternatives {
if !slices.Contains(options, item) { if !slices.Contains(options, item) {
options = append(options, item) options = append(options, item)
} }
@@ -748,34 +756,28 @@ func promptForStringList(
options = append(options, "add another") options = append(options, "add another")
selected := make([]string, len(existing)) selected := make([]string, len(defaults))
copy(selected, existing) copy(selected, defaults)
for { for {
prompt := &survey.MultiSelect{ newSelected := []string{}
if err := survey.AskOne(&survey.MultiSelect{
Message: name, Message: name,
Options: options, Options: options,
Default: selected, Default: selected,
PageSize: 20, PageSize: 20,
} }, &newSelected); err != nil {
if err := survey.AskOne(prompt, &selected); err != nil {
return nil, err return nil, err
} }
selected = newSelected
if slices.Contains(selected, "add another") { if slices.Contains(selected, "add another") {
selected = slices.DeleteFunc(selected, func(s string) bool { return s == "add another" }) selected = slices.DeleteFunc(selected, func(s string) bool { return s == "add another" })
singular := name
if strings.HasSuffix(singular, "s") {
singular = singular[:len(singular)-1]
}
newPrompt := &survey.Input{
Message: fmt.Sprintf("enter new %s", singular),
}
var newItem string var newItem string
if err := survey.AskOne(newPrompt, &newItem); err != nil { if err := survey.AskOne(&survey.Input{
Message: fmt.Sprintf("enter new %s", strings.TrimSuffix(name, "s")),
}, &newItem); err != nil {
return nil, err return nil, err
} }
@@ -938,7 +940,7 @@ func gitSync(ctx context.Context, signer nostr.Keyer) (nip34.Repository, *nip34.
func fetchFromRemotes(ctx context.Context, targetDir string, repo nip34.Repository) { func fetchFromRemotes(ctx context.Context, targetDir string, repo nip34.Repository) {
// fetch from each grasp remote // fetch from each grasp remote
for _, grasp := range repo.Relays { for _, grasp := range repo.Relays {
remoteName := "nip34/grasp/" + strings.Split(grasp, "/")[2] remoteName := gitRemoteName(grasp)
logverbose("fetching from %s...\n", remoteName) logverbose("fetching from %s...\n", remoteName)
fetchCmd := exec.Command("git", "fetch", remoteName) fetchCmd := exec.Command("git", "fetch", remoteName)
@@ -964,14 +966,16 @@ func gitSetupRemotes(ctx context.Context, dir string, repo nip34.Repository) {
return return
} }
// delete all nip34/grasp/ remotes // delete all nip34/grasp/ remotes that we don't have anymore in repo
remotes := strings.Split(strings.TrimSpace(string(output)), "\n") remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
for i, remote := range remotes { for i, remote := range remotes {
remote = strings.TrimSpace(remote) remote = strings.TrimSpace(remote)
remotes[i] = remote remotes[i] = remote
if strings.HasPrefix(remote, "nip34/grasp/") { if strings.HasPrefix(remote, "nip34/grasp/") {
if !slices.Contains(repo.Relays, nostr.NormalizeURL(remote[12:])) { graspURL := rebuildGraspURLFromRemote(remote)
if !slices.Contains(repo.Relays, nostr.NormalizeURL(graspURL)) {
delCmd := exec.Command("git", "remote", "remove", remote) delCmd := exec.Command("git", "remote", "remove", remote)
if dir != "" { if dir != "" {
delCmd.Dir = dir delCmd.Dir = dir
@@ -985,7 +989,7 @@ func gitSetupRemotes(ctx context.Context, dir string, repo nip34.Repository) {
// create new remotes for each grasp server // create new remotes for each grasp server
for _, relay := range repo.Relays { for _, relay := range repo.Relays {
remote := "nip34/grasp/" + strings.TrimPrefix(relay, "wss://") remote := gitRemoteName(relay)
if !slices.Contains(remotes, remote) { if !slices.Contains(remotes, remote) {
// construct the git URL // construct the git URL
@@ -1391,8 +1395,6 @@ func figureOutBranches(c *cli.Command, refspec string, isPush bool) (
return localBranch, remoteBranch, nil return localBranch, remoteBranch, nil
} }
func graspServerHost(s string) string { return strings.SplitN(nostr.NormalizeURL(s), "/", 3)[2] }
type Nip34Config struct { type Nip34Config struct {
Identifier string `json:"identifier"` Identifier string `json:"identifier"`
Name string `json:"name"` Name string `json:"name"`
@@ -1474,3 +1476,18 @@ func (localConfig Nip34Config) ToRepository() nip34.Repository {
return localRepo return localRepo
} }
func gitRemoteName(graspURL string) string {
host := graspServerHost(graspURL)
host = strings.Replace(host, ":", "__", 1)
return "nip34/grasp/" + host
}
func rebuildGraspURLFromRemote(remoteName string) string {
host := strings.TrimPrefix(remoteName, "nip34/grasp/")
return strings.Replace(host, "__", ":", 1)
}
func graspServerHost(s string) string {
return strings.SplitN(nostr.NormalizeURL(s), "/", 3)[2]
}