branch_names.rs

  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}