diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/push.rs | 31 | ||||
| -rw-r--r-- | src/lib/repo_state.rs | 7 |
3 files changed, 37 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf5312..f52d353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 18 | 18 | ||
| 19 | ### Fixed | 19 | ### Fixed |
| 20 | 20 | ||
| 21 | - Annotated tags missing from `git-remote-nostr` list output; peeled `^{}` refs were stripped when parsing the nostr state event, so git could not resolve the tag to a commit and `git fetch --prune` deleted it; existing repos with affected state events are self-healed on the next push | ||
| 21 | - Fallback signer relays updated: replaced `nsec.app` with `bucket.coracle.social` and `nos.lol` for nostrconnect resilience | 22 | - Fallback signer relays updated: replaced `nsec.app` with `bucket.coracle.social` and `nos.lol` for nostrconnect resilience |
| 22 | - `merge-base` tag in PR events generated by `git push` of a `pr/` branch was set to the parent of the PR tip instead of the actual base commit; multi-commit PRs showed only 1 commit when applied via `ngit apply` | 23 | - `merge-base` tag in PR events generated by `git push` of a `pr/` branch was set to the parent of the PR tip instead of the actual base commit; multi-commit PRs showed only 1 commit when applied via `ngit apply` |
| 23 | - `git-remote-nostr` list now advertises the newest state event whose OIDs are all confirmed present on a git server or locally, rather than unconditionally using the latest nostr state event; this prevents catastrophic fetch/clone failures when a state event was published before the corresponding git push completed | 24 | - `git-remote-nostr` list now advertises the newest state event whose OIDs are all confirmed present on a git server or locally, rather than unconditionally using the latest nostr state event; this prevents catastrophic fetch/clone failures when a state event was published before the corresponding git push completed |
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index 870f22d..ed0f7df 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs | |||
| @@ -925,6 +925,37 @@ fn generate_updated_state( | |||
| 925 | ) -> Result<HashMap<String, String>> { | 925 | ) -> Result<HashMap<String, String>> { |
| 926 | let mut new_state = existing_state.clone(); | 926 | let mut new_state = existing_state.clone(); |
| 927 | 927 | ||
| 928 | // Backfill missing ^{} peeled refs for any annotated tags already in the | ||
| 929 | // state. State events published before this fix only stored the tag object | ||
| 930 | // OID; without the corresponding ^{} entry git cannot resolve the tag to a | ||
| 931 | // commit and treats it as missing (git fetch --prune deletes it). We fix | ||
| 932 | // this opportunistically on every push so affected repos self-heal without | ||
| 933 | // requiring manual intervention. | ||
| 934 | let tag_refs: Vec<(String, String)> = new_state | ||
| 935 | .iter() | ||
| 936 | .filter(|(k, _)| k.starts_with("refs/tags/") && !k.ends_with("^{}")) | ||
| 937 | .map(|(k, v)| (k.clone(), v.clone())) | ||
| 938 | .collect(); | ||
| 939 | for (ref_name, tag_oid) in tag_refs { | ||
| 940 | let peeled_key = format!("{ref_name}^{{}}"); | ||
| 941 | if new_state.contains_key(&peeled_key) { | ||
| 942 | continue; | ||
| 943 | } | ||
| 944 | // check if the stored OID is a tag object (annotated tag) | ||
| 945 | if let Ok(oid) = git2::Oid::from_str(&tag_oid) { | ||
| 946 | if git_repo | ||
| 947 | .git_repo | ||
| 948 | .find_object(oid, Some(git2::ObjectType::Tag)) | ||
| 949 | .is_ok() | ||
| 950 | { | ||
| 951 | // peel to the commit the annotated tag points to | ||
| 952 | if let Ok(commit_oid) = git_repo.get_commit_or_tip_of_reference(&ref_name) { | ||
| 953 | new_state.insert(peeled_key, commit_oid.to_string()); | ||
| 954 | } | ||
| 955 | } | ||
| 956 | } | ||
| 957 | } | ||
| 958 | |||
| 928 | for refspec in refspecs { | 959 | for refspec in refspecs { |
| 929 | let (from, to) = refspec_to_from_to(refspec)?; | 960 | let (from, to) = refspec_to_from_to(refspec)?; |
| 930 | if from.is_empty() { | 961 | if from.is_empty() { |
diff --git a/src/lib/repo_state.rs b/src/lib/repo_state.rs index 345f05c..223fe56 100644 --- a/src/lib/repo_state.rs +++ b/src/lib/repo_state.rs | |||
| @@ -22,11 +22,14 @@ impl RepoState { | |||
| 22 | let mut state = HashMap::new(); | 22 | let mut state = HashMap::new(); |
| 23 | for tag in event.tags.iter() { | 23 | for tag in event.tags.iter() { |
| 24 | if let Some(name) = tag.as_slice().first() { | 24 | if let Some(name) = tag.as_slice().first() { |
| 25 | // include ^{} peeled refs for annotated tags: git requires | ||
| 26 | // both "<tag-oid> refs/tags/v1.0.0" and | ||
| 27 | // "<commit-oid> refs/tags/v1.0.0^{}" in the list output so | ||
| 28 | // it can resolve the tag to a commit. without the ^{} line | ||
| 29 | // git fetch --prune deletes the tag as unresolvable. | ||
| 25 | if ["refs/heads/", "refs/tags", "HEAD"] | 30 | if ["refs/heads/", "refs/tags", "HEAD"] |
| 26 | .iter() | 31 | .iter() |
| 27 | .any(|s| name.starts_with(*s)) | 32 | .any(|s| name.starts_with(*s)) |
| 28 | // dont include dereferenced tags | ||
| 29 | && !name.ends_with("^{}") | ||
| 30 | { | 33 | { |
| 31 | if let Some(value) = tag.as_slice().get(1) { | 34 | if let Some(value) = tag.as_slice().get(1) { |
| 32 | if Oid::from_str(value).is_ok() || value.contains("ref: refs/") { | 35 | if Oid::from_str(value).is_ok() || value.contains("ref: refs/") { |