mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-08 16:48:51 +00:00
git: fix a bunch of small bugs.
This commit is contained in:
209
git.go
209
git.go
@@ -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]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user