Repository: jj-vcs/jj (rust)

Issue:
FR: point trunk() at upstream rather than origin by default
**Is your feature request related to a problem? Please describe.**
I often use git repos that have been modified by the github cli. When you clone a repo, the remote is named origin, when you use the tool to fork a repo, the origin remote is renamed upstream and your fork is now the origin.

**Describe the solution you'd like**
When initializing jj (`jj git init --colocate`), the `trunk()` alias should point at main@upstream if the remote exists rather than main@origin.

**Describe alternatives you've considered**
It's possible that this should have some configuration behind it, but this seems like it's a reasonable default. If there's push back against it being a default, I'd still like to be able to override this behavior so that what I believe to be sensible is acheivable.

**Additional context**
```
❯ jj git init --colocate    
Done importing changes from the underlying Git repo.
Setting the revset alias `trunk()` to `master@origin`
Hint: The following remote bookmarks aren't associated with the existing local bookmarks:
  master@origin
  master@upstream
Hint: Run the following command to keep local bookmarks updated on future pulls:
  jj bookmark track master@origin master@upstream
Initialized repo in "."
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a16480fb310..40983980fb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -48,6 +48,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 * `jj log`, `jj evolog` and `jj op log` output can now be anonymized with the
   `builtin_log_redacted` and `builtin_op_log_redacted` templates.
 
+* `jj git init` now checks for an `upstream` remote in addition to `origin` when
+  setting the repository-level `trunk()` alias. The `upstream` remote takes
+  precedence over `origin` if both exist.
+
 * Add the `jj touch` command, which modifies a revision's metadata. This can be
   used to generate a new change-id, which may help resolve some divergences.
 
diff --git a/cli/src/commands/git/init.rs b/cli/src/commands/git/init.rs
index ae3672c3849..198cb870280 100644
--- a/cli/src/commands/git/init.rs
+++ b/cli/src/commands/git/init.rs
@@ -240,27 +240,39 @@ fn init_git_refs(
     Ok(repo)
 }
 
-// Set repository level `trunk()` alias to the default branch for "origin".
+// Set repository level `trunk()` alias to the default branch.
+// Checks "upstream" first, then "origin" as fallback.
 pub fn maybe_set_repository_level_trunk_alias(
     ui: &Ui,
     workspace_command: &WorkspaceCommandHelper,
 ) -> Result<(), CommandError> {
     let git_repo = git::get_git_repo(workspace_command.repo().store())?;
-    if let Some(reference) = git_repo
-        .try_find_reference("refs/remotes/origin/HEAD")
-        .map_err(internal_error)?
-    {
-        if let Some(reference_name) = reference.target().try_name() {
-            if let Some((GitRefKind::Bookmark, symbol)) = str::from_utf8(reference_name.as_bstr())
-                .ok()
-                .and_then(|name| parse_git_ref(name.as_ref()))
-            {
-                // TODO: Can we assume the symbolic target points to the same remote?
-                let symbol = symbol.name.to_remote_symbol("origin".as_ref());
-                write_repository_level_trunk_alias(ui, workspace_command.repo_path(), symbol)?;
+
+    // Try "upstream" first, then fall back to "origin"
+    for remote in ["upstream", "origin"] {
+        let ref_name = format!("refs/remotes/{remote}/HEAD");
+        if let Some(reference) = git_repo
+            .try_find_reference(&ref_name)
+            .map_err(internal_error)?
+        {
+            // Found a HEAD reference for this remote. Even if we can't parse it,
+            // we should stop here and not try other remotes because it doesn't
+            // really make sense if "origin" were to be set as the default if we
+            // know "upstream" exists.
+            if let Some(reference_name) = reference.target().try_name() {
+                if let Some((GitRefKind::Bookmark, symbol)) =
+                    str::from_utf8(reference_name.as_bstr())
+                        .ok()
+                        .and_then(|name| parse_git_ref(name.as_ref()))
+                {
+                    // TODO: Can we assume the symbolic target points to the same remote?
+                    let symbol = symbol.name.to_remote_symbol(remote.as_ref());
+                    write_repository_level_trunk_alias(ui, workspace_command.repo_path(), symbol)?;
+                }
             }
-        };
-    };
+            return Ok(());
+        }
+    }
 
     Ok(())
 }


Test patch:
diff --git a/cli/tests/test_git_init.rs b/cli/tests/test_git_init.rs
index 433a61a3fda..ca55e24f8db 100644
--- a/cli/tests/test_git_init.rs
+++ b/cli/tests/test_git_init.rs
@@ -234,6 +234,84 @@ fn test_git_init_external_import_trunk(bare: bool) {
     }
 }
 
+#[test]
+fn test_git_init_external_import_trunk_upstream_takes_precedence() {
+    let test_env = TestEnvironment::default();
+    let git_repo_path = test_env.env_root().join("git-repo");
+    let git_repo = init_git_repo(&git_repo_path, false);
+
+    let oid = git_repo
+        .find_reference("refs/heads/my-bookmark")
+        .unwrap()
+        .id();
+
+    // Add both upstream and origin remotes with different default branches
+    // upstream has "develop" as default
+    git_repo
+        .reference(
+            "refs/remotes/upstream/develop",
+            oid.detach(),
+            gix::refs::transaction::PreviousValue::MustNotExist,
+            "create upstream remote ref",
+        )
+        .unwrap();
+
+    git::set_symbolic_reference(
+        &git_repo,
+        "refs/remotes/upstream/HEAD",
+        "refs/remotes/upstream/develop",
+    );
+
+    // origin has "trunk" as default
+    git_repo
+        .reference(
+            "refs/remotes/origin/trunk",
+            oid.detach(),
+            gix::refs::transaction::PreviousValue::MustNotExist,
+            "create origin remote ref",
+        )
+        .unwrap();
+
+    git::set_symbolic_reference(
+        &git_repo,
+        "refs/remotes/origin/HEAD",
+        "refs/remotes/origin/trunk",
+    );
+
+    let output = test_env.run_jj_in(
+        ".",
+        [
+            "git",
+            "init",
+            "repo",
+            "--git-repo",
+            git_repo_path.to_str().unwrap(),
+        ],
+    );
+    insta::allow_duplicates! {
+    insta::assert_snapshot!(output, @r#"
+    ------- stderr -------
+    Done importing changes from the underlying Git repo.
+    Setting the revset alias `trunk()` to `develop@upstream`
+    Working copy  (@) now at: sqpuoqvx ed6b5138 (empty) (no description set)
+    Parent commit (@-)      : nntyzxmz e80a42cc develop@upstream my-bookmark trunk@origin | My commit message
+    Added 1 files, modified 0 files, removed 0 files
+    Initialized repo in "repo"
+    [EOF]
+    "#);
+    }
+
+    // "trunk()" alias should be set to "upstream"'s default, not "origin"'s
+    let work_dir = test_env.work_dir("repo");
+    let output = work_dir.run_jj(["config", "list", "--repo", "revset-aliases.\"trunk()\""]);
+    insta::allow_duplicates! {
+        insta::assert_snapshot!(output, @r#"
+        revset-aliases."trunk()" = "develop@upstream"
+        [EOF]
+        "#);
+    }
+}
+
 #[test]
 fn test_git_init_external_ignore_working_copy() {
     let test_env = TestEnvironment::default();