mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Use historical typewriter names for agent branch names (#50611)
Replace the auto-generated `agent-[hash]` branch naming scheme with names based on historical typewriter models (e.g. `olivetti-a3f9b2c1`, `selectric-7d2e4f01`). A static list of 49 typewriter names is used as the pool. When selecting a name, existing branch names are checked—each branch is split on its last `-` to extract the typewriter prefix, and any taken prefixes are excluded from the candidate set. A random name is then picked from the remaining candidates and suffixed with an 8-character alphanumeric hash. The name selection logic accepts injected RNG and a list of disallowed names, making it straightforward to test deterministically with `#[gpui::test]` and `StdRng`. Closes AI-71 (No release notes because this is feature-flagged.) Release Notes: - N/A
This commit is contained in:
parent
c0f5d21a88
commit
33c78fad33
4 changed files with 915 additions and 35 deletions
|
|
@ -67,7 +67,6 @@ use language_model::{ConfigurationError, LanguageModelRegistry};
|
|||
use project::project_settings::ProjectSettings;
|
||||
use project::{Project, ProjectPath, Worktree};
|
||||
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
|
||||
use rand::Rng as _;
|
||||
use rules_library::{RulesLibrary, open_rules_library};
|
||||
use search::{BufferSearchBar, buffer_search};
|
||||
use settings::{Settings, update_settings_file};
|
||||
|
|
@ -2042,21 +2041,6 @@ impl AgentPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_agent_branch_name() -> String {
|
||||
let mut rng = rand::rng();
|
||||
let id: String = (0..8)
|
||||
.map(|_| {
|
||||
let idx: u8 = rng.random_range(0..36);
|
||||
if idx < 10 {
|
||||
(b'0' + idx) as char
|
||||
} else {
|
||||
(b'a' + idx - 10) as char
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
format!("agent-{id}")
|
||||
}
|
||||
|
||||
/// Partitions the project's visible worktrees into git-backed repositories
|
||||
/// and plain (non-git) paths. Git repos will have worktrees created for
|
||||
/// them; non-git paths are carried over to the new workspace as-is.
|
||||
|
|
@ -2256,8 +2240,6 @@ impl AgentPanel {
|
|||
self.worktree_creation_status = Some(WorktreeCreationStatus::Creating);
|
||||
cx.notify();
|
||||
|
||||
let branch_name = Self::generate_agent_branch_name();
|
||||
|
||||
let (git_repos, non_git_paths) = self.classify_worktrees(cx);
|
||||
|
||||
if git_repos.is_empty() {
|
||||
|
|
@ -2269,28 +2251,18 @@ impl AgentPanel {
|
|||
return;
|
||||
}
|
||||
|
||||
// Kick off branch listing as early as possible so it can run
|
||||
// concurrently with the remaining synchronous setup work.
|
||||
let branch_receivers: Vec<_> = git_repos
|
||||
.iter()
|
||||
.map(|repo| repo.update(cx, |repo, _cx| repo.branches()))
|
||||
.collect();
|
||||
|
||||
let worktree_directory_setting = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.worktree_directory
|
||||
.clone();
|
||||
|
||||
let (creation_infos, path_remapping) = match Self::start_worktree_creations(
|
||||
&git_repos,
|
||||
&branch_name,
|
||||
&worktree_directory_setting,
|
||||
cx,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
self.set_worktree_creation_error(
|
||||
format!("Failed to validate worktree directory: {err}").into(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (dock_structure, open_file_paths) = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
|
|
@ -2307,6 +2279,63 @@ impl AgentPanel {
|
|||
.downcast::<workspace::MultiWorkspace>();
|
||||
|
||||
let task = cx.spawn_in(window, async move |this, cx| {
|
||||
// Await the branch listings we kicked off earlier.
|
||||
let mut existing_branches = Vec::new();
|
||||
for result in futures::future::join_all(branch_receivers).await {
|
||||
match result {
|
||||
Ok(Ok(branches)) => {
|
||||
for branch in branches {
|
||||
existing_branches.push(branch.name().to_string());
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
Err::<(), _>(err).log_err();
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let existing_branch_refs: Vec<&str> =
|
||||
existing_branches.iter().map(|s| s.as_str()).collect();
|
||||
let mut rng = rand::rng();
|
||||
let branch_name =
|
||||
match crate::branch_names::generate_branch_name(&existing_branch_refs, &mut rng) {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.set_worktree_creation_error(
|
||||
"Failed to generate a branch name: all typewriter names are taken"
|
||||
.into(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let (creation_infos, path_remapping) = match this.update_in(cx, |_this, _window, cx| {
|
||||
Self::start_worktree_creations(
|
||||
&git_repos,
|
||||
&branch_name,
|
||||
&worktree_directory_setting,
|
||||
cx,
|
||||
)
|
||||
}) {
|
||||
Ok(Ok(result)) => result,
|
||||
Ok(Err(err)) | Err(err) => {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.set_worktree_creation_error(
|
||||
format!("Failed to validate worktree directory: {err}").into(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let created_paths = match Self::await_and_rollback_on_failure(creation_infos, cx).await
|
||||
{
|
||||
Ok(paths) => paths,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ mod agent_diff;
|
|||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod agent_registry_ui;
|
||||
mod branch_names;
|
||||
mod buffer_codegen;
|
||||
mod completion_provider;
|
||||
mod config_options;
|
||||
|
|
|
|||
847
crates/agent_ui/src/branch_names.rs
Normal file
847
crates/agent_ui/src/branch_names.rs
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
use collections::HashSet;
|
||||
use rand::Rng;
|
||||
|
||||
/// Names of historical typewriter brands, for use in auto-generated branch names.
|
||||
/// (Hyphens and parens have been dropped so that the branch names are one-word.)
|
||||
///
|
||||
/// Thanks to https://typewriterdatabase.com/alph.0.brands for the names!
|
||||
const TYPEWRITER_NAMES: &[&str] = &[
|
||||
"abeille",
|
||||
"acme",
|
||||
"addo",
|
||||
"adler",
|
||||
"adlerette",
|
||||
"adlerita",
|
||||
"admiral",
|
||||
"agamli",
|
||||
"agar",
|
||||
"agidel",
|
||||
"agil",
|
||||
"aguia",
|
||||
"aguila",
|
||||
"ahram",
|
||||
"aigle",
|
||||
"ajax",
|
||||
"aktiv",
|
||||
"ala",
|
||||
"alba",
|
||||
"albus",
|
||||
"alexander",
|
||||
"alexis",
|
||||
"alfa",
|
||||
"allen",
|
||||
"alonso",
|
||||
"alpina",
|
||||
"amata",
|
||||
"amaya",
|
||||
"amka",
|
||||
"anavi",
|
||||
"anderson",
|
||||
"andina",
|
||||
"antares",
|
||||
"apex",
|
||||
"apsco",
|
||||
"aquila",
|
||||
"archo",
|
||||
"ardita",
|
||||
"argyle",
|
||||
"aristocrat",
|
||||
"aristokrat",
|
||||
"arlington",
|
||||
"armstrong",
|
||||
"arpha",
|
||||
"artus",
|
||||
"astoria",
|
||||
"atlantia",
|
||||
"atlantic",
|
||||
"atlas",
|
||||
"augusta",
|
||||
"aurora",
|
||||
"austro",
|
||||
"automatic",
|
||||
"avanti",
|
||||
"avona",
|
||||
"azzurra",
|
||||
"bajnok",
|
||||
"baldwin",
|
||||
"balkan",
|
||||
"baltica",
|
||||
"baltimore",
|
||||
"barlock",
|
||||
"barr",
|
||||
"barrat",
|
||||
"bartholomew",
|
||||
"bashkiriya",
|
||||
"bavaria",
|
||||
"beaucourt",
|
||||
"beko",
|
||||
"belka",
|
||||
"bennett",
|
||||
"bennington",
|
||||
"berni",
|
||||
"bianca",
|
||||
"bijou",
|
||||
"bing",
|
||||
"bisei",
|
||||
"biser",
|
||||
"bluebird",
|
||||
"bolida",
|
||||
"borgo",
|
||||
"boston",
|
||||
"boyce",
|
||||
"bradford",
|
||||
"brandenburg",
|
||||
"brigitte",
|
||||
"briton",
|
||||
"brooks",
|
||||
"brosette",
|
||||
"buddy",
|
||||
"burns",
|
||||
"burroughs",
|
||||
"byron",
|
||||
"calanda",
|
||||
"caligraph",
|
||||
"cappel",
|
||||
"cardinal",
|
||||
"carissima",
|
||||
"carlem",
|
||||
"carlton",
|
||||
"carmen",
|
||||
"cawena",
|
||||
"cella",
|
||||
"celtic",
|
||||
"century",
|
||||
"champignon",
|
||||
"cherryland",
|
||||
"chevron",
|
||||
"chicago",
|
||||
"cicero",
|
||||
"cifra",
|
||||
"citizen",
|
||||
"claudia",
|
||||
"cleveland",
|
||||
"clover",
|
||||
"coffman",
|
||||
"cole",
|
||||
"columbia",
|
||||
"commercial",
|
||||
"companion",
|
||||
"concentra",
|
||||
"concord",
|
||||
"concordia",
|
||||
"conover",
|
||||
"constanta",
|
||||
"consul",
|
||||
"conta",
|
||||
"contenta",
|
||||
"contimat",
|
||||
"contina",
|
||||
"continento",
|
||||
"cornelia",
|
||||
"coronado",
|
||||
"cosmopolita",
|
||||
"courier",
|
||||
"craftamatic",
|
||||
"crandall",
|
||||
"crown",
|
||||
"culema",
|
||||
"dactyle",
|
||||
"dankers",
|
||||
"dart",
|
||||
"daugherty",
|
||||
"davis",
|
||||
"dayton",
|
||||
"dea",
|
||||
"delmar",
|
||||
"densmore",
|
||||
"depantio",
|
||||
"diadema",
|
||||
"dial",
|
||||
"diamant",
|
||||
"diana",
|
||||
"dictatype",
|
||||
"diplomat",
|
||||
"diskret",
|
||||
"dolfus",
|
||||
"dollar",
|
||||
"domus",
|
||||
"drake",
|
||||
"draper",
|
||||
"duplex",
|
||||
"durabel",
|
||||
"dynacord",
|
||||
"eagle",
|
||||
"eclipse",
|
||||
"edelmann",
|
||||
"edelweiss",
|
||||
"edison",
|
||||
"edita",
|
||||
"edland",
|
||||
"efka",
|
||||
"eldorado",
|
||||
"electa",
|
||||
"electromatic",
|
||||
"elektro",
|
||||
"elgin",
|
||||
"elliot",
|
||||
"emerson",
|
||||
"emka",
|
||||
"emona",
|
||||
"empire",
|
||||
"engadine",
|
||||
"engler",
|
||||
"erfurt",
|
||||
"erika",
|
||||
"esko",
|
||||
"essex",
|
||||
"eureka",
|
||||
"europa",
|
||||
"everest",
|
||||
"everlux",
|
||||
"excelsior",
|
||||
"express",
|
||||
"fabers",
|
||||
"facit",
|
||||
"fairbanks",
|
||||
"faktotum",
|
||||
"famos",
|
||||
"federal",
|
||||
"felio",
|
||||
"fidat",
|
||||
"filius",
|
||||
"fips",
|
||||
"fish",
|
||||
"fitch",
|
||||
"fleet",
|
||||
"florida",
|
||||
"flott",
|
||||
"flyer",
|
||||
"flying",
|
||||
"fontana",
|
||||
"ford",
|
||||
"forto",
|
||||
"fortuna",
|
||||
"fox",
|
||||
"framo",
|
||||
"franconia",
|
||||
"franklin",
|
||||
"friden",
|
||||
"frolio",
|
||||
"furstenberg",
|
||||
"galesburg",
|
||||
"galiette",
|
||||
"gallia",
|
||||
"garbell",
|
||||
"gardner",
|
||||
"geka",
|
||||
"generation",
|
||||
"genia",
|
||||
"geniatus",
|
||||
"gerda",
|
||||
"gisela",
|
||||
"glashutte",
|
||||
"gloria",
|
||||
"godrej",
|
||||
"gossen",
|
||||
"gourland",
|
||||
"grandjean",
|
||||
"granta",
|
||||
"granville",
|
||||
"graphic",
|
||||
"gritzner",
|
||||
"groma",
|
||||
"guhl",
|
||||
"guidonia",
|
||||
"gundka",
|
||||
"hacabo",
|
||||
"haddad",
|
||||
"halberg",
|
||||
"halda",
|
||||
"hall",
|
||||
"hammond",
|
||||
"hammonia",
|
||||
"hanford",
|
||||
"hansa",
|
||||
"harmony",
|
||||
"harris",
|
||||
"hartford",
|
||||
"hassia",
|
||||
"hatch",
|
||||
"heady",
|
||||
"hebronia",
|
||||
"hebros",
|
||||
"hega",
|
||||
"helios",
|
||||
"helma",
|
||||
"herald",
|
||||
"hercules",
|
||||
"hermes",
|
||||
"herold",
|
||||
"heros",
|
||||
"hesperia",
|
||||
"hogar",
|
||||
"hooven",
|
||||
"hopkins",
|
||||
"horton",
|
||||
"hugin",
|
||||
"hungaria",
|
||||
"hurtu",
|
||||
"iberia",
|
||||
"idea",
|
||||
"ideal",
|
||||
"imperia",
|
||||
"impo",
|
||||
"industria",
|
||||
"industrio",
|
||||
"ingersoll",
|
||||
"international",
|
||||
"invicta",
|
||||
"irene",
|
||||
"iris",
|
||||
"iskra",
|
||||
"ivitsa",
|
||||
"ivriah",
|
||||
"jackson",
|
||||
"janalif",
|
||||
"janos",
|
||||
"jolux",
|
||||
"juki",
|
||||
"junior",
|
||||
"juventa",
|
||||
"juwel",
|
||||
"kamkap",
|
||||
"kamo",
|
||||
"kanzler",
|
||||
"kappel",
|
||||
"karli",
|
||||
"karstadt",
|
||||
"keaton",
|
||||
"kenbar",
|
||||
"keystone",
|
||||
"kim",
|
||||
"klein",
|
||||
"kneist",
|
||||
"knoch",
|
||||
"koh",
|
||||
"kolibri",
|
||||
"kolumbus",
|
||||
"komet",
|
||||
"kondor",
|
||||
"koniger",
|
||||
"konryu",
|
||||
"kontor",
|
||||
"kosmopolit",
|
||||
"krypton",
|
||||
"lambert",
|
||||
"lasalle",
|
||||
"lectra",
|
||||
"leframa",
|
||||
"lemair",
|
||||
"lemco",
|
||||
"liberty",
|
||||
"libia",
|
||||
"liga",
|
||||
"lignose",
|
||||
"lilliput",
|
||||
"lindeteves",
|
||||
"linowriter",
|
||||
"listvitsa",
|
||||
"ludolf",
|
||||
"lutece",
|
||||
"luxa",
|
||||
"lyubava",
|
||||
"mafra",
|
||||
"magnavox",
|
||||
"maher",
|
||||
"majestic",
|
||||
"majitouch",
|
||||
"manhattan",
|
||||
"mapuua",
|
||||
"marathon",
|
||||
"marburger",
|
||||
"maritsa",
|
||||
"maruzen",
|
||||
"maskelyne",
|
||||
"masspro",
|
||||
"matous",
|
||||
"mccall",
|
||||
"mccool",
|
||||
"mcloughlin",
|
||||
"mead",
|
||||
"mechno",
|
||||
"mehano",
|
||||
"meiselbach",
|
||||
"melbi",
|
||||
"melior",
|
||||
"melotyp",
|
||||
"mentor",
|
||||
"mepas",
|
||||
"mercedesia",
|
||||
"mercurius",
|
||||
"mercury",
|
||||
"merkur",
|
||||
"merritt",
|
||||
"merz",
|
||||
"messa",
|
||||
"meteco",
|
||||
"meteor",
|
||||
"micron",
|
||||
"mignon",
|
||||
"mikro",
|
||||
"minerva",
|
||||
"mirian",
|
||||
"mirina",
|
||||
"mitex",
|
||||
"molle",
|
||||
"monac",
|
||||
"monarch",
|
||||
"mondiale",
|
||||
"monica",
|
||||
"monofix",
|
||||
"monopol",
|
||||
"monpti",
|
||||
"monta",
|
||||
"montana",
|
||||
"montgomery",
|
||||
"moon",
|
||||
"morgan",
|
||||
"morris",
|
||||
"morse",
|
||||
"moya",
|
||||
"moyer",
|
||||
"munson",
|
||||
"musicwriter",
|
||||
"nadex",
|
||||
"nakajima",
|
||||
"neckermann",
|
||||
"neubert",
|
||||
"neya",
|
||||
"ninety",
|
||||
"nisa",
|
||||
"noiseless",
|
||||
"noor",
|
||||
"nora",
|
||||
"nord",
|
||||
"norden",
|
||||
"norica",
|
||||
"norma",
|
||||
"norman",
|
||||
"north",
|
||||
"nototyp",
|
||||
"nova",
|
||||
"novalevi",
|
||||
"odell",
|
||||
"odhner",
|
||||
"odo",
|
||||
"odoma",
|
||||
"ohio",
|
||||
"ohtani",
|
||||
"oliva",
|
||||
"oliver",
|
||||
"olivetti",
|
||||
"olympia",
|
||||
"omega",
|
||||
"optima",
|
||||
"orbis",
|
||||
"orel",
|
||||
"orga",
|
||||
"oriette",
|
||||
"orion",
|
||||
"orn",
|
||||
"orplid",
|
||||
"pacior",
|
||||
"pagina",
|
||||
"parisienne",
|
||||
"passat",
|
||||
"pearl",
|
||||
"peerless",
|
||||
"perfect",
|
||||
"perfecta",
|
||||
"perkeo",
|
||||
"perkins",
|
||||
"perlita",
|
||||
"pettypet",
|
||||
"phoenix",
|
||||
"piccola",
|
||||
"picht",
|
||||
"pinnock",
|
||||
"pionier",
|
||||
"plurotyp",
|
||||
"plutarch",
|
||||
"pneumatic",
|
||||
"pocket",
|
||||
"polyglott",
|
||||
"polygraph",
|
||||
"pontiac",
|
||||
"portable",
|
||||
"portex",
|
||||
"pozzi",
|
||||
"premier",
|
||||
"presto",
|
||||
"primavera",
|
||||
"progress",
|
||||
"protos",
|
||||
"pterotype",
|
||||
"pullman",
|
||||
"pulsatta",
|
||||
"quick",
|
||||
"racer",
|
||||
"radio",
|
||||
"rally",
|
||||
"rand",
|
||||
"readers",
|
||||
"reed",
|
||||
"referent",
|
||||
"reff",
|
||||
"regent",
|
||||
"regia",
|
||||
"regina",
|
||||
"rekord",
|
||||
"reliable",
|
||||
"reliance",
|
||||
"remagg",
|
||||
"rembrandt",
|
||||
"remer",
|
||||
"remington",
|
||||
"remsho",
|
||||
"remstar",
|
||||
"remtor",
|
||||
"reporters",
|
||||
"resko",
|
||||
"rex",
|
||||
"rexpel",
|
||||
"rheinita",
|
||||
"rheinmetall",
|
||||
"rival",
|
||||
"roberts",
|
||||
"robotron",
|
||||
"rocher",
|
||||
"rochester",
|
||||
"roebuck",
|
||||
"rofa",
|
||||
"roland",
|
||||
"rooy",
|
||||
"rover",
|
||||
"roxy",
|
||||
"roy",
|
||||
"royal",
|
||||
"rundstatler",
|
||||
"sabaudia",
|
||||
"sabb",
|
||||
"saleem",
|
||||
"salter",
|
||||
"sampo",
|
||||
"sarafan",
|
||||
"saturn",
|
||||
"saxonia",
|
||||
"schade",
|
||||
"schapiro",
|
||||
"schreibi",
|
||||
"scripta",
|
||||
"sears",
|
||||
"secor",
|
||||
"selectric",
|
||||
"selekta",
|
||||
"senator",
|
||||
"sense",
|
||||
"senta",
|
||||
"serd",
|
||||
"shilling",
|
||||
"shimade",
|
||||
"shimer",
|
||||
"sholes",
|
||||
"shuang",
|
||||
"siegfried",
|
||||
"siemag",
|
||||
"silma",
|
||||
"silver",
|
||||
"simplex",
|
||||
"simtype",
|
||||
"singer",
|
||||
"smith",
|
||||
"soemtron",
|
||||
"sonja",
|
||||
"speedwriter",
|
||||
"sphinx",
|
||||
"starlet",
|
||||
"stearns",
|
||||
"steel",
|
||||
"stella",
|
||||
"steno",
|
||||
"sterling",
|
||||
"stoewer",
|
||||
"stolzenberg",
|
||||
"stott",
|
||||
"strangfeld",
|
||||
"sture",
|
||||
"stylotyp",
|
||||
"sun",
|
||||
"superba",
|
||||
"superia",
|
||||
"supermetall",
|
||||
"surety",
|
||||
"swintec",
|
||||
"swissa",
|
||||
"talbos",
|
||||
"talleres",
|
||||
"tatrapoint",
|
||||
"taurus",
|
||||
"taylorix",
|
||||
"tell",
|
||||
"tempotype",
|
||||
"tippco",
|
||||
"titania",
|
||||
"tops",
|
||||
"towa",
|
||||
"toyo",
|
||||
"tradition",
|
||||
"transatlantic",
|
||||
"traveller",
|
||||
"trebla",
|
||||
"triumph",
|
||||
"turia",
|
||||
"typatune",
|
||||
"typen",
|
||||
"typorium",
|
||||
"ugro",
|
||||
"ultima",
|
||||
"unda",
|
||||
"underwood",
|
||||
"unica",
|
||||
"unitype",
|
||||
"ursula",
|
||||
"utax",
|
||||
"varityper",
|
||||
"vasanta",
|
||||
"vendex",
|
||||
"venus",
|
||||
"victor",
|
||||
"victoria",
|
||||
"video",
|
||||
"viking",
|
||||
"vira",
|
||||
"virotyp",
|
||||
"visigraph",
|
||||
"vittoria",
|
||||
"volcan",
|
||||
"vornado",
|
||||
"voss",
|
||||
"vultur",
|
||||
"waltons",
|
||||
"wanamaker",
|
||||
"wanderer",
|
||||
"ward",
|
||||
"warner",
|
||||
"waterloo",
|
||||
"waverley",
|
||||
"wayne",
|
||||
"webster",
|
||||
"wedgefield",
|
||||
"welco",
|
||||
"wellington",
|
||||
"wellon",
|
||||
"weltblick",
|
||||
"westphalia",
|
||||
"wiedmer",
|
||||
"williams",
|
||||
"wilson",
|
||||
"winkel",
|
||||
"winsor",
|
||||
"wizard",
|
||||
"woodstock",
|
||||
"woodwards",
|
||||
"yatran",
|
||||
"yost",
|
||||
"zenit",
|
||||
"zentronik",
|
||||
"zeta",
|
||||
"zeya",
|
||||
];
|
||||
|
||||
/// Picks a typewriter name that isn't already taken by an existing branch.
|
||||
///
|
||||
/// Each entry in `existing_branches` is expected to be a full branch name
|
||||
/// like `"olivetti-a3f9b2c1"`. The prefix before the last `'-'` is treated
|
||||
/// as the taken typewriter name. Branches without a `'-'` are ignored.
|
||||
///
|
||||
/// Returns `None` when every name in the pool is already taken.
|
||||
pub fn pick_typewriter_name(
|
||||
existing_branches: &[&str],
|
||||
rng: &mut impl Rng,
|
||||
) -> Option<&'static str> {
|
||||
let disallowed: HashSet<&str> = existing_branches
|
||||
.iter()
|
||||
.filter_map(|branch| branch.rsplit_once('-').map(|(prefix, _)| prefix))
|
||||
.collect();
|
||||
|
||||
let available: Vec<&'static str> = TYPEWRITER_NAMES
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|name| !disallowed.contains(name))
|
||||
.collect();
|
||||
|
||||
if available.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let index = rng.random_range(0..available.len());
|
||||
Some(available[index])
|
||||
}
|
||||
|
||||
/// Generates a branch name like `"olivetti-a3f9b2c1"` by picking a typewriter
|
||||
/// name that isn't already taken and appending an 8-character alphanumeric hash.
|
||||
///
|
||||
/// Returns `None` when every typewriter name in the pool is already taken.
|
||||
pub fn generate_branch_name(existing_branches: &[&str], rng: &mut impl Rng) -> Option<String> {
|
||||
let typewriter_name = pick_typewriter_name(existing_branches, rng)?;
|
||||
let hash: String = (0..8)
|
||||
.map(|_| {
|
||||
let idx: u8 = rng.random_range(0..36);
|
||||
if idx < 10 {
|
||||
(b'0' + idx) as char
|
||||
} else {
|
||||
(b'a' + idx - 10) as char
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(format!("{typewriter_name}-{hash}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
fn test_pick_typewriter_name_with_no_disallowed(mut rng: StdRng) {
|
||||
let name = pick_typewriter_name(&[], &mut rng);
|
||||
assert!(name.is_some());
|
||||
assert!(TYPEWRITER_NAMES.contains(&name.unwrap()));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
fn test_pick_typewriter_name_excludes_taken_names(mut rng: StdRng) {
|
||||
let branch_names = &["olivetti-abc12345", "selectric-def67890"];
|
||||
let name = pick_typewriter_name(branch_names, &mut rng).unwrap();
|
||||
assert_ne!(name, "olivetti");
|
||||
assert_ne!(name, "selectric");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_pick_typewriter_name_all_taken(mut rng: StdRng) {
|
||||
let branch_names: Vec<String> = TYPEWRITER_NAMES
|
||||
.iter()
|
||||
.map(|name| format!("{name}-00000000"))
|
||||
.collect();
|
||||
let branch_name_refs: Vec<&str> = branch_names.iter().map(|s| s.as_str()).collect();
|
||||
let name = pick_typewriter_name(&branch_name_refs, &mut rng);
|
||||
assert!(name.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
fn test_pick_typewriter_name_ignores_branches_without_hyphen(mut rng: StdRng) {
|
||||
let branch_names = &["main", "develop", "feature"];
|
||||
let name = pick_typewriter_name(branch_names, &mut rng);
|
||||
assert!(name.is_some());
|
||||
assert!(TYPEWRITER_NAMES.contains(&name.unwrap()));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
fn test_generate_branch_name_format(mut rng: StdRng) {
|
||||
let branch_name = generate_branch_name(&[], &mut rng).unwrap();
|
||||
let (prefix, suffix) = branch_name.rsplit_once('-').unwrap();
|
||||
assert!(TYPEWRITER_NAMES.contains(&prefix));
|
||||
assert_eq!(suffix.len(), 8);
|
||||
assert!(suffix.chars().all(|c| c.is_ascii_alphanumeric()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_generate_branch_name_returns_none_when_exhausted(mut rng: StdRng) {
|
||||
let branch_names: Vec<String> = TYPEWRITER_NAMES
|
||||
.iter()
|
||||
.map(|name| format!("{name}-00000000"))
|
||||
.collect();
|
||||
let branch_name_refs: Vec<&str> = branch_names.iter().map(|s| s.as_str()).collect();
|
||||
let result = generate_branch_name(&branch_name_refs, &mut rng);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_generate_branch_name_never_reuses_taken_prefix(mut rng: StdRng) {
|
||||
let existing = &["olivetti-123abc", "selectric-def456"];
|
||||
let branch_name = generate_branch_name(existing, &mut rng).unwrap();
|
||||
let (prefix, _) = branch_name.rsplit_once('-').unwrap();
|
||||
assert_ne!(prefix, "olivetti");
|
||||
assert_ne!(prefix, "selectric");
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_generate_branch_name_avoids_multiple_taken_prefixes(mut rng: StdRng) {
|
||||
let existing = &[
|
||||
"olivetti-aaa11111",
|
||||
"selectric-bbb22222",
|
||||
"corona-ccc33333",
|
||||
"remington-ddd44444",
|
||||
"underwood-eee55555",
|
||||
];
|
||||
let taken_prefixes: HashSet<&str> = existing
|
||||
.iter()
|
||||
.filter_map(|b| b.rsplit_once('-').map(|(prefix, _)| prefix))
|
||||
.collect();
|
||||
let branch_name = generate_branch_name(existing, &mut rng).unwrap();
|
||||
let (prefix, _) = branch_name.rsplit_once('-').unwrap();
|
||||
assert!(
|
||||
!taken_prefixes.contains(prefix),
|
||||
"generated prefix {prefix:?} collides with an existing branch"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_generate_branch_name_with_varied_hash_suffixes(mut rng: StdRng) {
|
||||
let existing = &[
|
||||
"olivetti-aaaaaaaa",
|
||||
"olivetti-bbbbbbbb",
|
||||
"olivetti-cccccccc",
|
||||
];
|
||||
let branch_name = generate_branch_name(existing, &mut rng).unwrap();
|
||||
let (prefix, _) = branch_name.rsplit_once('-').unwrap();
|
||||
assert_ne!(
|
||||
prefix, "olivetti",
|
||||
"should avoid olivetti regardless of how many variants exist"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typewriter_names_are_valid() {
|
||||
let mut seen = HashSet::default();
|
||||
for &name in TYPEWRITER_NAMES {
|
||||
assert!(
|
||||
seen.insert(name),
|
||||
"duplicate entry in TYPEWRITER_NAMES: {name:?}"
|
||||
);
|
||||
}
|
||||
|
||||
for window in TYPEWRITER_NAMES.windows(2) {
|
||||
assert!(
|
||||
window[0] <= window[1],
|
||||
"TYPEWRITER_NAMES is not sorted: {0:?} should come after {1:?}",
|
||||
window[1],
|
||||
window[0],
|
||||
);
|
||||
}
|
||||
|
||||
for &name in TYPEWRITER_NAMES {
|
||||
assert!(
|
||||
!name.contains('-'),
|
||||
"TYPEWRITER_NAMES entry contains a hyphen: {name:?}"
|
||||
);
|
||||
}
|
||||
|
||||
for &name in TYPEWRITER_NAMES {
|
||||
assert!(
|
||||
name.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()),
|
||||
"TYPEWRITER_NAMES entry is not lowercase: {name:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ ignore-hidden = false
|
|||
extend-exclude = [
|
||||
".git/",
|
||||
|
||||
# Typewriter model names used for agent branch names aren't typos.
|
||||
"crates/agent_ui/src/branch_names.rs",
|
||||
|
||||
# Contributor names aren't typos.
|
||||
".mailmap",
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue