1use collections::HashSet;
2use rand::Rng;
3
4/// Names of historical typewriter brands, for use in auto-generated branch names.
5/// (Hyphens and parens have been dropped so that the branch names are one-word.)
6///
7/// Thanks to https://typewriterdatabase.com/alph.0.brands for the names!
8const TYPEWRITER_NAMES: &[&str] = &[
9 "abeille",
10 "acme",
11 "addo",
12 "adler",
13 "adlerette",
14 "adlerita",
15 "admiral",
16 "agamli",
17 "agar",
18 "agidel",
19 "agil",
20 "aguia",
21 "aguila",
22 "aigle",
23 "ahram",
24 "ajax",
25 "aktiv",
26 "ala",
27 "alba",
28 "albus",
29 "alexander",
30 "alexis",
31 "alfa",
32 "allen",
33 "alonso",
34 "alpina",
35 "amata",
36 "amaya",
37 "amka",
38 "anavi",
39 "anderson",
40 "andina",
41 "antares",
42 "apex",
43 "apsco",
44 "aquila",
45 "archo",
46 "ardita",
47 "argyle",
48 "aristocrat",
49 "aristokrat",
50 "arlington",
51 "armstrong",
52 "arpha",
53 "artus",
54 "astoria",
55 "atlantia",
56 "atlantic",
57 "atlas",
58 "augusta",
59 "aurora",
60 "austro",
61 "automatic",
62 "avanti",
63 "avona",
64 "azzurra",
65 "bajnok",
66 "baldwin",
67 "balkan",
68 "baltica",
69 "baltimore",
70 "barlock",
71 "barr",
72 "barrat",
73 "bartholomew",
74 "bashkiriya",
75 "bavaria",
76 "beaucourt",
77 "beko",
78 "belka",
79 "bennett",
80 "bennington",
81 "berni",
82 "bianca",
83 "bijou",
84 "bing",
85 "bisei",
86 "biser",
87 "bluebird",
88 "bolida",
89 "borgo",
90 "boston",
91 "boyce",
92 "bradford",
93 "brandenburg",
94 "brigitte",
95 "briton",
96 "brooks",
97 "brosette",
98 "buddy",
99 "burns",
100 "burroughs",
101 "byron",
102 "calanda",
103 "caligraph",
104 "cappel",
105 "cardinal",
106 "carissima",
107 "carlem",
108 "carlton",
109 "carmen",
110 "cawena",
111 "cella",
112 "celtic",
113 "century",
114 "champignon",
115 "cherryland",
116 "chevron",
117 "chicago",
118 "cicero",
119 "cifra",
120 "citizen",
121 "claudia",
122 "cleveland",
123 "clover",
124 "coffman",
125 "cole",
126 "columbia",
127 "commercial",
128 "companion",
129 "concentra",
130 "concord",
131 "concordia",
132 "conover",
133 "constanta",
134 "consul",
135 "conta",
136 "contenta",
137 "contimat",
138 "contina",
139 "continento",
140 "cornelia",
141 "coronado",
142 "cosmopolita",
143 "courier",
144 "craftamatic",
145 "crandall",
146 "crown",
147 "culema",
148 "dactyle",
149 "dankers",
150 "dart",
151 "daugherty",
152 "davis",
153 "dayton",
154 "depantio",
155 "dea",
156 "delmar",
157 "densmore",
158 "diadema",
159 "dial",
160 "diamant",
161 "diana",
162 "dictatype",
163 "diplomat",
164 "diskret",
165 "dolfus",
166 "dollar",
167 "domus",
168 "drake",
169 "draper",
170 "duplex",
171 "durabel",
172 "dynacord",
173 "eagle",
174 "eclipse",
175 "edelmann",
176 "edelweiss",
177 "edison",
178 "edita",
179 "edland",
180 "efka",
181 "eldorado",
182 "electa",
183 "electromatic",
184 "elektro",
185 "elgin",
186 "elliot",
187 "emerson",
188 "emka",
189 "emona",
190 "empire",
191 "engadine",
192 "engler",
193 "erfurt",
194 "erika",
195 "esko",
196 "essex",
197 "eureka",
198 "europa",
199 "everest",
200 "everlux",
201 "excelsior",
202 "express",
203 "fabers",
204 "facit",
205 "fairbanks",
206 "faktotum",
207 "famos",
208 "federal",
209 "felio",
210 "fidat",
211 "filius",
212 "fips",
213 "fish",
214 "fitch",
215 "fleet",
216 "florida",
217 "flott",
218 "flyer",
219 "flying",
220 "fontana",
221 "ford",
222 "forto",
223 "fortuna",
224 "fox",
225 "framo",
226 "franconia",
227 "franklin",
228 "friden",
229 "frolio",
230 "furstenberg",
231 "galesburg",
232 "galiette",
233 "gallia",
234 "garbell",
235 "gardner",
236 "geka",
237 "generation",
238 "genia",
239 "geniatus",
240 "gerda",
241 "gisela",
242 "glashutte",
243 "gloria",
244 "godrej",
245 "gossen",
246 "gourland",
247 "grandjean",
248 "granta",
249 "granville",
250 "graphic",
251 "gritzner",
252 "groma",
253 "guhl",
254 "guidonia",
255 "gundka",
256 "hacabo",
257 "haddad",
258 "halberg",
259 "halda",
260 "hall",
261 "hammond",
262 "hammonia",
263 "harmony",
264 "hanford",
265 "hansa",
266 "harris",
267 "hartford",
268 "hassia",
269 "hatch",
270 "heady",
271 "hebronia",
272 "hebros",
273 "hega",
274 "helios",
275 "helma",
276 "herald",
277 "hercules",
278 "hermes",
279 "herold",
280 "heros",
281 "hesperia",
282 "hogar",
283 "hooven",
284 "hopkins",
285 "horton",
286 "hugin",
287 "hungaria",
288 "hurtu",
289 "iberia",
290 "idea",
291 "ideal",
292 "imperia",
293 "impo",
294 "industria",
295 "industrio",
296 "ingersoll",
297 "international",
298 "invicta",
299 "irene",
300 "iris",
301 "iskra",
302 "ivitsa",
303 "ivriah",
304 "jackson",
305 "janalif",
306 "janos",
307 "jolux",
308 "juki",
309 "junior",
310 "juventa",
311 "juwel",
312 "kamkap",
313 "kamo",
314 "kanzler",
315 "kappel",
316 "karli",
317 "karstadt",
318 "keaton",
319 "kenbar",
320 "keystone",
321 "kim",
322 "klein",
323 "kneist",
324 "knoch",
325 "koh",
326 "kolibri",
327 "kolumbus",
328 "komet",
329 "kondor",
330 "koniger",
331 "konryu",
332 "kontor",
333 "kosmopolit",
334 "krypton",
335 "lambert",
336 "lasalle",
337 "lectra",
338 "leframa",
339 "lemair",
340 "lemco",
341 "liberty",
342 "libia",
343 "liga",
344 "lignose",
345 "lilliput",
346 "lindeteves",
347 "linowriter",
348 "listvitsa",
349 "ludolf",
350 "lutece",
351 "luxa",
352 "lyubava",
353 "mafra",
354 "magnavox",
355 "maher",
356 "majestic",
357 "majitouch",
358 "manhattan",
359 "mapuua",
360 "marathon",
361 "marburger",
362 "maritsa",
363 "maruzen",
364 "maskelyne",
365 "masspro",
366 "matous",
367 "mccall",
368 "mccool",
369 "mcloughlin",
370 "mead",
371 "mechno",
372 "mehano",
373 "meiselbach",
374 "melbi",
375 "melior",
376 "melotyp",
377 "mentor",
378 "mepas",
379 "mercedesia",
380 "mercurius",
381 "mercury",
382 "merkur",
383 "merritt",
384 "merz",
385 "messa",
386 "meteco",
387 "meteor",
388 "micron",
389 "mignon",
390 "mikro",
391 "minerva",
392 "mirian",
393 "mirina",
394 "mitex",
395 "molle",
396 "monac",
397 "monarch",
398 "mondiale",
399 "monica",
400 "monofix",
401 "monopol",
402 "monpti",
403 "monta",
404 "montana",
405 "montgomery",
406 "moon",
407 "morgan",
408 "morris",
409 "morse",
410 "moya",
411 "moyer",
412 "munson",
413 "musicwriter",
414 "nadex",
415 "nakajima",
416 "neckermann",
417 "neubert",
418 "neya",
419 "ninety",
420 "nisa",
421 "noiseless",
422 "noor",
423 "nora",
424 "nord",
425 "norden",
426 "norica",
427 "norma",
428 "norman",
429 "north",
430 "nototyp",
431 "nova",
432 "novalevi",
433 "odell",
434 "odhner",
435 "odo",
436 "odoma",
437 "ohio",
438 "ohtani",
439 "oliva",
440 "oliver",
441 "olivetti",
442 "olympia",
443 "omega",
444 "optima",
445 "orbis",
446 "orel",
447 "orga",
448 "oriette",
449 "orion",
450 "orn",
451 "orplid",
452 "pacior",
453 "pagina",
454 "parisienne",
455 "passat",
456 "pearl",
457 "peerless",
458 "perfect",
459 "perfecta",
460 "perkeo",
461 "perkins",
462 "perlita",
463 "pettypet",
464 "phoenix",
465 "piccola",
466 "picht",
467 "pinnock",
468 "pionier",
469 "plurotyp",
470 "plutarch",
471 "pneumatic",
472 "pocket",
473 "polyglott",
474 "polygraph",
475 "pontiac",
476 "portable",
477 "portex",
478 "pozzi",
479 "premier",
480 "presto",
481 "primavera",
482 "progress",
483 "protos",
484 "pterotype",
485 "pullman",
486 "pulsatta",
487 "quick",
488 "racer",
489 "rand",
490 "radio",
491 "rally",
492 "readers",
493 "reed",
494 "referent",
495 "reff",
496 "regent",
497 "regia",
498 "regina",
499 "rekord",
500 "reliable",
501 "reliance",
502 "remsho",
503 "remagg",
504 "rembrandt",
505 "remer",
506 "remington",
507 "remstar",
508 "remtor",
509 "reporters",
510 "resko",
511 "rex",
512 "rexpel",
513 "rheinita",
514 "rheinmetall",
515 "rival",
516 "roberts",
517 "robotron",
518 "rocher",
519 "rochester",
520 "roebuck",
521 "rofa",
522 "roland",
523 "rooy",
524 "rover",
525 "roxy",
526 "roy",
527 "royal",
528 "rundstatler",
529 "sabaudia",
530 "sabb",
531 "saleem",
532 "salter",
533 "sampo",
534 "sarafan",
535 "saturn",
536 "saxonia",
537 "schade",
538 "schapiro",
539 "schreibi",
540 "scripta",
541 "sears",
542 "secor",
543 "selectric",
544 "selekta",
545 "senator",
546 "sense",
547 "senta",
548 "serd",
549 "shilling",
550 "shimade",
551 "shimer",
552 "sholes",
553 "shuang",
554 "siegfried",
555 "siemag",
556 "silma",
557 "silver",
558 "simplex",
559 "simtype",
560 "singer",
561 "smith",
562 "soemtron",
563 "sonja",
564 "speedwriter",
565 "sphinx",
566 "starlet",
567 "stearns",
568 "steel",
569 "stella",
570 "steno",
571 "sterling",
572 "stoewer",
573 "stolzenberg",
574 "stott",
575 "strangfeld",
576 "sture",
577 "stylotyp",
578 "sun",
579 "superba",
580 "superia",
581 "supermetall",
582 "surety",
583 "swintec",
584 "swissa",
585 "talbos",
586 "talleres",
587 "tatrapoint",
588 "taurus",
589 "taylorix",
590 "tell",
591 "tempotype",
592 "society",
593 "tippco",
594 "titania",
595 "tops",
596 "towa",
597 "toyo",
598 "tradition",
599 "transatlantic",
600 "traveller",
601 "trebla",
602 "triumph",
603 "turia",
604 "typatune",
605 "typen",
606 "typorium",
607 "ugro",
608 "ultima",
609 "unda",
610 "underwood",
611 "unica",
612 "unitype",
613 "ursula",
614 "utax",
615 "varityper",
616 "vasanta",
617 "vendex",
618 "venus",
619 "victor",
620 "victoria",
621 "video",
622 "viking",
623 "vira",
624 "virotyp",
625 "visigraph",
626 "vittoria",
627 "volcan",
628 "vornado",
629 "voss",
630 "vultur",
631 "waltons",
632 "wanamaker",
633 "wanderer",
634 "ward",
635 "warner",
636 "waterloo",
637 "waverley",
638 "wayne",
639 "webster",
640 "wedgefield",
641 "welco",
642 "wellington",
643 "wellon",
644 "weltblick",
645 "westphalia",
646 "wiedmer",
647 "williams",
648 "wilson",
649 "winkel",
650 "winsor",
651 "wizard",
652 "woodstock",
653 "woodwards",
654 "yatran",
655 "yost",
656 "zenit",
657 "zentronik",
658 "zeta",
659 "zeya",
660];
661
662/// Picks a typewriter name that isn't already taken by an existing branch.
663///
664/// Each entry in `existing_branches` is expected to be a full branch name
665/// like `"olivetti-a3f9b2c1"`. The prefix before the last `'-'` is treated
666/// as the taken typewriter name. Branches without a `'-'` are ignored.
667///
668/// Returns `None` when every name in the pool is already taken.
669pub fn pick_typewriter_name(
670 existing_branches: &[&str],
671 rng: &mut impl Rng,
672) -> Option<&'static str> {
673 let disallowed: HashSet<&str> = existing_branches
674 .iter()
675 .filter_map(|branch| branch.rsplit_once('-').map(|(prefix, _)| prefix))
676 .collect();
677
678 let available: Vec<&'static str> = TYPEWRITER_NAMES
679 .iter()
680 .copied()
681 .filter(|name| !disallowed.contains(name))
682 .collect();
683
684 if available.is_empty() {
685 return None;
686 }
687
688 let index = rng.random_range(0..available.len());
689 Some(available[index])
690}
691
692/// Generates a branch name like `"olivetti-a3f9b2c1"` by picking a typewriter
693/// name that isn't already taken and appending an 8-character alphanumeric hash.
694///
695/// Returns `None` when every typewriter name in the pool is already taken.
696pub fn generate_branch_name(existing_branches: &[&str], rng: &mut impl Rng) -> Option<String> {
697 let typewriter_name = pick_typewriter_name(existing_branches, rng)?;
698 let hash: String = (0..8)
699 .map(|_| {
700 let idx: u8 = rng.random_range(0..36);
701 if idx < 10 {
702 (b'0' + idx) as char
703 } else {
704 (b'a' + idx - 10) as char
705 }
706 })
707 .collect();
708 Some(format!("{typewriter_name}-{hash}"))
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use rand::rngs::StdRng;
715
716 #[gpui::test(iterations = 10)]
717 fn test_pick_typewriter_name_with_no_disallowed(mut rng: StdRng) {
718 let name = pick_typewriter_name(&[], &mut rng);
719 assert!(name.is_some());
720 assert!(TYPEWRITER_NAMES.contains(&name.unwrap()));
721 }
722
723 #[gpui::test(iterations = 10)]
724 fn test_pick_typewriter_name_excludes_taken_names(mut rng: StdRng) {
725 let branch_names = &["olivetti-abc12345", "selectric-def67890"];
726 let name = pick_typewriter_name(branch_names, &mut rng).unwrap();
727 assert_ne!(name, "olivetti");
728 assert_ne!(name, "selectric");
729 }
730
731 #[gpui::test]
732 fn test_pick_typewriter_name_all_taken(mut rng: StdRng) {
733 let branch_names: Vec<String> = TYPEWRITER_NAMES
734 .iter()
735 .map(|name| format!("{name}-00000000"))
736 .collect();
737 let branch_name_refs: Vec<&str> = branch_names.iter().map(|s| s.as_str()).collect();
738 let name = pick_typewriter_name(&branch_name_refs, &mut rng);
739 assert!(name.is_none());
740 }
741
742 #[gpui::test(iterations = 10)]
743 fn test_pick_typewriter_name_ignores_branches_without_hyphen(mut rng: StdRng) {
744 let branch_names = &["main", "develop", "feature"];
745 let name = pick_typewriter_name(branch_names, &mut rng);
746 assert!(name.is_some());
747 assert!(TYPEWRITER_NAMES.contains(&name.unwrap()));
748 }
749
750 #[gpui::test(iterations = 10)]
751 fn test_generate_branch_name_format(mut rng: StdRng) {
752 let branch_name = generate_branch_name(&[], &mut rng).unwrap();
753 let (prefix, suffix) = branch_name.rsplit_once('-').unwrap();
754 assert!(TYPEWRITER_NAMES.contains(&prefix));
755 assert_eq!(suffix.len(), 8);
756 assert!(suffix.chars().all(|c| c.is_ascii_alphanumeric()));
757 }
758
759 #[gpui::test]
760 fn test_generate_branch_name_returns_none_when_exhausted(mut rng: StdRng) {
761 let branch_names: Vec<String> = TYPEWRITER_NAMES
762 .iter()
763 .map(|name| format!("{name}-00000000"))
764 .collect();
765 let branch_name_refs: Vec<&str> = branch_names.iter().map(|s| s.as_str()).collect();
766 let result = generate_branch_name(&branch_name_refs, &mut rng);
767 assert!(result.is_none());
768 }
769}