1#![allow(clippy::all)]
5use execute::Execute;
6use icu::collator::{Collator, CollatorOptions, Numeric, Strength};
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9use std::io::Write;
10use std::path::PathBuf;
11use std::{
12 env::temp_dir,
13 fs::{remove_file, File},
14 path::Path,
15 process::{Command, Stdio},
16};
17use version_compare::{Cmp, Version};
18
19use super::packages::PackageInfo;
20use super::paths::get_project_root_path;
21use super::utils::{package_scope_name_version, strip_trailing_newline};
22
23#[cfg(feature = "napi")]
24#[napi(object)]
25#[derive(Debug, Clone, Deserialize, Serialize)]
26pub struct Commit {
27 pub hash: String,
28 pub author_name: String,
29 pub author_email: String,
30 pub author_date: String,
31 pub message: String,
32}
33
34#[cfg(not(feature = "napi"))]
35#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct Commit {
38 pub hash: String,
39 pub author_name: String,
40 pub author_email: String,
41 pub author_date: String,
42 pub message: String,
43}
44
45#[cfg(feature = "napi")]
46#[napi(object)]
47#[derive(Debug, Clone, Deserialize, Serialize)]
48pub struct RemoteTags {
49 pub hash: String,
50 pub tag: String,
51}
52
53#[cfg(not(feature = "napi"))]
54#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct RemoteTags {
57 pub hash: String,
58 pub tag: String,
59}
60
61#[cfg(feature = "napi")]
62#[napi(object)]
63#[derive(Debug, Clone, Deserialize, Serialize)]
64pub struct PublishTagInfo {
65 pub hash: String,
66 pub tag: String,
67 pub package: String,
68}
69
70#[cfg(not(feature = "napi"))]
71#[derive(Debug, Clone, Deserialize, Serialize)]
72pub struct PublishTagInfo {
74 pub hash: String,
75 pub tag: String,
76 pub package: String,
77}
78
79pub fn git_add_all(cwd: &String) -> Result<bool, std::io::Error> {
81 let mut git_add = Command::new("git");
82
83 git_add.current_dir(cwd.to_string()).arg("add").arg(".");
84
85 git_add.stdout(Stdio::piped());
86 git_add.stderr(Stdio::piped());
87
88 let output = git_add.execute_output().unwrap();
89
90 if output.status.success() {
91 Ok(true)
92 } else {
93 Ok(false)
94 }
95}
96
97pub fn git_add(cwd: &String, file: &String) -> Result<bool, std::io::Error> {
99 let mut git_add = Command::new("git");
100
101 git_add.current_dir(cwd.to_string()).arg("add").arg(file);
102
103 git_add.stdout(Stdio::piped());
104 git_add.stderr(Stdio::piped());
105
106 let output = git_add.execute_output().unwrap();
107
108 if output.status.success() {
109 Ok(true)
110 } else {
111 Ok(false)
112 }
113}
114
115pub fn git_config(username: &String, email: &String, cwd: &String) -> Result<bool, std::io::Error> {
117 let mut git_config_user = Command::new("git");
118
119 git_config_user
120 .current_dir(cwd.to_string())
121 .arg("config")
122 .arg("user.name")
123 .arg(username);
124
125 git_config_user.stdout(Stdio::piped());
126 git_config_user.stderr(Stdio::piped());
127
128 let output_user = git_config_user.execute_output().unwrap();
129
130 let mut git_config_email = Command::new("git");
131 git_config_email
132 .current_dir(cwd.to_string())
133 .arg("config")
134 .arg("user.email")
135 .arg(email);
136
137 git_config_email.stdout(Stdio::piped());
138 git_config_email.stderr(Stdio::piped());
139
140 let output_email = git_config_email.execute_output().unwrap();
141 let status = output_user.status.success() == output_email.status.success();
142
143 if status {
144 Ok(true)
145 } else {
146 Ok(false)
147 }
148}
149
150pub fn git_fetch_all(
152 cwd: Option<String>,
153 fetch_tags: Option<bool>,
154) -> Result<bool, std::io::Error> {
155 let current_working_dir = match cwd {
156 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
157 None => get_project_root_path(None).unwrap(),
158 };
159
160 let mut command = Command::new("git");
161 command.arg("fetch").arg("origin");
162
163 if fetch_tags.unwrap_or(false) {
164 command.arg("--tags").arg("--force");
165 }
166
167 command.current_dir(¤t_working_dir);
168
169 command.stdout(Stdio::piped());
170 command.stderr(Stdio::piped());
171
172 let output = command.execute_output().unwrap();
173
174 if output.status.success() {
175 Ok(true)
176 } else {
177 Ok(false)
178 }
179}
180
181pub fn get_diverged_commit(refer: String, cwd: Option<String>) -> Option<String> {
183 let current_working_dir = match cwd {
184 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
185 None => get_project_root_path(None).unwrap(),
186 };
187
188 let mut command = Command::new("git");
189 command.arg("merge-base").arg(&refer).arg("HEAD");
190 command.current_dir(¤t_working_dir);
191
192 command.stdout(Stdio::piped());
193 command.stderr(Stdio::piped());
194
195 let output = command.execute_output().unwrap();
196
197 if !output.status.success() {
198 return None;
199 }
200
201 let output = String::from_utf8(output.stdout).unwrap();
202
203 Some(strip_trailing_newline(&output))
204}
205
206pub fn git_current_sha(cwd: Option<String>) -> String {
208 let current_working_dir = match cwd {
209 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
210 None => get_project_root_path(None).unwrap(),
211 };
212
213 let mut command = Command::new("git");
214 command.arg("rev-parse").arg("--short").arg("HEAD");
215
216 command.current_dir(¤t_working_dir);
217
218 command.stdout(Stdio::piped());
219 command.stderr(Stdio::piped());
220
221 let output = command.execute_output().unwrap();
222
223 let hash = String::from_utf8(output.stdout).unwrap();
224 strip_trailing_newline(&hash)
225}
226
227pub fn git_previous_sha(cwd: Option<String>) -> String {
229 let current_working_dir = match cwd {
230 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
231 None => get_project_root_path(None).unwrap(),
232 };
233
234 let mut command = Command::new("git");
235 command.arg("rev-parse").arg("--short").arg("HEAD~1");
236
237 command.current_dir(¤t_working_dir);
238
239 command.stdout(Stdio::piped());
240 command.stderr(Stdio::piped());
241
242 let output = command.execute_output().unwrap();
243
244 let hash = String::from_utf8(output.stdout).unwrap();
245
246 strip_trailing_newline(&hash)
247}
248
249pub fn git_first_sha(cwd: Option<String>, branch: Option<String>) -> String {
251 let current_working_dir = match cwd {
252 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
253 None => get_project_root_path(None).unwrap(),
254 };
255
256 let branch = match branch {
257 Some(branch) => branch,
258 None => String::from("main"),
259 };
260
261 let mut command = Command::new("git");
262 command
263 .arg("log")
264 .arg(format!("{}..HEAD", branch))
265 .arg("--online")
266 .arg("--pretty=format:%h")
267 .arg("|")
268 .arg("tail")
269 .arg("-1");
270
271 command.current_dir(¤t_working_dir);
272
273 command.stdout(Stdio::piped());
274 command.stderr(Stdio::piped());
275
276 let output = command.execute_output().unwrap();
277
278 let hash = String::from_utf8(output.stdout).unwrap();
279
280 strip_trailing_newline(&hash)
281}
282
283pub fn git_workdir_unclean(cwd: Option<String>) -> bool {
285 let current_working_dir = match cwd {
286 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
287 None => get_project_root_path(None).unwrap(),
288 };
289
290 let mut command = Command::new("git");
291 command.arg("status").arg("--porcelain");
292
293 command.current_dir(¤t_working_dir);
294
295 command.stdout(Stdio::piped());
296 command.stderr(Stdio::piped());
297
298 let output = command.execute_output().unwrap();
299
300 let output = String::from_utf8(output.stdout).unwrap();
301 let result = strip_trailing_newline(&output);
302
303 if result.is_empty() {
304 return false;
305 }
306
307 true
308}
309
310pub fn git_current_branch(cwd: Option<String>) -> Option<String> {
312 let current_working_dir = match cwd {
313 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
314 None => get_project_root_path(None).unwrap(),
315 };
316
317 let mut command = Command::new("git");
318 command.arg("rev-parse").arg("--abbrev-ref").arg("HEAD");
319
320 command.current_dir(¤t_working_dir);
321
322 command.stdout(Stdio::piped());
323 command.stderr(Stdio::piped());
324
325 let output = command.execute_output().unwrap();
326
327 let output = String::from_utf8(output.stdout).unwrap();
328 let result = strip_trailing_newline(&output);
329
330 if result.is_empty() {
331 return None;
332 }
333
334 Some(result)
335}
336
337pub fn git_branch_from_commit(commit: String, cwd: Option<String>) -> Option<String> {
339 let current_working_dir = match cwd {
340 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
341 None => get_project_root_path(None).unwrap(),
342 };
343
344 let mut command = Command::new("git");
346 command
347 .arg("--no-pager")
348 .arg("branch")
349 .arg("--no-color")
350 .arg("--no-column")
351 .arg("--format")
352 .arg(r#""%(refname:lstrip=2)""#)
353 .arg("--contains")
354 .arg(&commit);
355
356 command.current_dir(¤t_working_dir);
357
358 command.stdout(Stdio::piped());
359 command.stderr(Stdio::piped());
360
361 let output = command.execute_output().unwrap();
362
363 let output = String::from_utf8(output.stdout).unwrap();
364 let result = strip_trailing_newline(&output);
365
366 if result.is_empty() {
367 return None;
368 }
369
370 Some(result)
371}
372
373pub fn git_tag(
375 tag: String,
376 message: Option<String>,
377 cwd: Option<String>,
378) -> Result<bool, std::io::Error> {
379 let current_working_dir = match cwd {
380 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
381 None => get_project_root_path(None).unwrap(),
382 };
383
384 let default_message = &tag;
385 let msg = message.or(Some(default_message.to_string())).unwrap();
386
387 let mut command = Command::new("git");
388 command.arg("tag").arg("-a").arg(&tag).arg("-m").arg(&msg);
389
390 command.current_dir(¤t_working_dir);
391
392 command.stdout(Stdio::piped());
393 command.stderr(Stdio::piped());
394
395 let output = command.execute_output().unwrap();
396
397 if output.status.success() {
398 Ok(true)
399 } else {
400 Ok(false)
401 }
402}
403
404pub fn git_push(cwd: Option<String>, follow_tags: Option<bool>) -> Result<bool, std::io::Error> {
406 let current_working_dir = match cwd {
407 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
408 None => get_project_root_path(None).unwrap(),
409 };
410
411 let mut command = Command::new("git");
412 command.arg("push");
413
414 if follow_tags.unwrap_or(false) {
415 command.arg("--follow-tags");
416 }
417
418 command.arg("--no-verify");
419 command.current_dir(¤t_working_dir);
420
421 command.stdout(Stdio::piped());
422 command.stderr(Stdio::piped());
423
424 let output = command.execute_output().unwrap();
425
426 if output.status.success() {
427 Ok(true)
428 } else {
429 Ok(false)
430 }
431}
432
433pub fn git_commit(
435 mut message: String,
436 body: Option<String>,
437 footer: Option<String>,
438 cwd: Option<String>,
439) -> Result<bool, std::io::Error> {
440 let current_working_dir = match cwd {
441 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
442 None => get_project_root_path(None).unwrap(),
443 };
444
445 if body.is_some() {
446 message.push_str("\n\n");
447 message.push_str(body.unwrap().as_str());
448 }
449
450 if footer.is_some() {
451 message.push_str("\n\n");
452 message.push_str(footer.unwrap().as_str());
453 }
454
455 let temp_dir = temp_dir();
456 let temp_file_path = temp_dir.join("commit_message.txt");
457
458 let mut file = File::create(&temp_file_path).unwrap();
459 file.write_all(message.as_bytes()).unwrap();
460
461 let file_path = temp_file_path.as_path();
462
463 let mut command = Command::new("git");
464 command
465 .arg("commit")
466 .arg("-F")
467 .arg(&file_path.to_str().unwrap())
468 .arg("--no-verify");
469
470 command.current_dir(¤t_working_dir);
471
472 command.stdout(Stdio::piped());
473 command.stderr(Stdio::piped());
474
475 let output = command.execute_output().unwrap();
476
477 remove_file(file_path).expect("Commit file not deleted");
478
479 if output.status.success() {
480 Ok(true)
481 } else {
482 Ok(false)
483 }
484}
485
486pub fn git_all_files_changed_since_sha(sha: String, cwd: Option<String>) -> Vec<String> {
489 let current_working_dir = match cwd {
490 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
491 None => get_project_root_path(None).unwrap(),
492 };
493
494 let mut command = Command::new("git");
495 command
496 .arg("--no-pager")
497 .arg("diff")
498 .arg("--name-only")
499 .arg(format!("{}", sha));
500 command.current_dir(¤t_working_dir);
501
502 command.stdout(Stdio::piped());
503 command.stderr(Stdio::piped());
504
505 let output = command.execute_output().unwrap();
506
507 if !output.status.success() {
508 return vec![];
509 }
510
511 let output = String::from_utf8(output.stdout).unwrap();
512 let root = Path::new(¤t_working_dir);
513
514 output
515 .split("\n")
516 .filter(|item| !item.trim().is_empty())
517 .map(|item| root.join(item))
518 .filter(|item| item.exists())
519 .map(|item| item.to_str().unwrap().to_string())
520 .collect::<Vec<String>>()
521}
522
523pub fn get_commits_since(
527 cwd: Option<String>,
528 since: Option<String>,
529 relative: Option<String>,
530) -> Vec<Commit> {
531 let current_working_dir = match cwd {
532 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
533 None => get_project_root_path(None).unwrap(),
534 };
535
536 const DELIMITER: &str = r#"#=#"#;
537 const BREAK_LINE: &str = r#"#+#"#;
538
539 let mut command = Command::new("git");
540 command
541 .arg("--no-pager")
542 .arg("log")
543 .arg(format!(
544 "--format={}%H{}%an{}%ae{}%ad{}%B{}",
545 DELIMITER, DELIMITER, DELIMITER, DELIMITER, DELIMITER, BREAK_LINE
546 ))
547 .arg("--date=rfc2822");
548
549 if let Some(since) = since {
550 command.arg(format!("{}..", since));
551 }
552
553 if let Some(relative) = relative {
554 command.arg("--");
555 command.arg(&relative);
556 }
557
558 command.current_dir(¤t_working_dir);
559
560 command.stdout(Stdio::piped());
561 command.stderr(Stdio::piped());
562
563 let output = command.execute_output().unwrap();
564
565 if !output.status.success() {
566 return vec![];
567 }
568
569 let output = String::from_utf8(output.stdout).unwrap();
570
571 output
572 .split(BREAK_LINE)
573 .filter(|item| !item.trim().is_empty())
574 .map(|item| {
575 let item_trimmed = item.trim();
576 let items = item_trimmed.split(DELIMITER).collect::<Vec<&str>>();
577
578 Commit {
579 hash: items.get(1).unwrap().to_string(),
580 author_name: items.get(2).unwrap().to_string(),
581 author_email: items.get(3).unwrap().to_string(),
582 author_date: items.get(4).unwrap().to_string(),
583 message: items.get(5).unwrap().to_string(),
584 }
585 })
586 .collect::<Vec<Commit>>()
587}
588
589pub fn get_remote_or_local_tags(cwd: Option<String>, local: Option<bool>) -> Vec<RemoteTags> {
591 let current_working_dir = match cwd {
592 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
593 None => get_project_root_path(None).unwrap(),
594 };
595
596 let mut command = Command::new("git");
597
598 match local {
599 Some(true) => command.arg("show-ref").arg("--tags"),
600 Some(false) => command.arg("ls-remote").arg("--tags").arg("origin"),
601 None => command.arg("ls-remote").arg("--tags").arg("origin"),
602 };
603
604 command.current_dir(¤t_working_dir);
605
606 command.stdout(Stdio::piped());
607 command.stderr(Stdio::piped());
608
609 let output = command.execute_output().unwrap();
610
611 if !output.status.success() {
612 return vec![];
613 }
614
615 let output = String::from_utf8(output.stdout).unwrap();
616
617 #[cfg(windows)]
618 const LINE_ENDING: &'static str = "\r\n";
619 #[cfg(not(windows))]
620 const LINE_ENDING: &'static str = "\n";
621
622 output
623 .trim()
624 .split(LINE_ENDING)
625 .filter(|tags| !tags.trim().is_empty())
626 .map(|tags| {
627 let hash_tags = Regex::new(r"\s+")
628 .unwrap()
629 .split(tags)
630 .collect::<Vec<&str>>();
631
632 RemoteTags {
633 hash: hash_tags.get(0).unwrap().to_string(),
634 tag: hash_tags.get(1).unwrap().to_string(),
635 }
636 })
637 .collect::<Vec<RemoteTags>>()
638}
639
640pub fn get_all_files_changed_since_branch(
643 package_info: &Vec<PackageInfo>,
644 branch: &String,
645 cwd: Option<String>,
646) -> Vec<String> {
647 let current_working_dir = match cwd {
648 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
649 None => get_project_root_path(None).unwrap(),
650 };
651
652 let mut all_files = vec![];
653
654 package_info.iter().for_each(|item| {
655 let files = git_all_files_changed_since_sha(
656 branch.to_string(),
657 Some(current_working_dir.to_string()),
658 );
659
660 let pkg_files = files
661 .iter()
662 .filter(|file| file.starts_with(item.package_path.as_str()))
663 .collect::<Vec<&String>>();
664
665 all_files.append(
666 &mut pkg_files
667 .iter()
668 .map(|file| file.to_string())
669 .collect::<Vec<String>>(),
670 );
671 });
672
673 all_files
674}
675
676pub fn get_last_known_publish_tag_info_for_package(
678 package_info: &PackageInfo,
679 cwd: Option<String>,
680) -> Option<PublishTagInfo> {
681 let current_working_dir = match cwd {
682 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
683 None => get_project_root_path(None).unwrap(),
684 };
685
686 let mut remote_tags =
687 get_remote_or_local_tags(Some(current_working_dir.to_string()), Some(false));
688 let mut local_tags =
689 get_remote_or_local_tags(Some(current_working_dir.to_string()), Some(true));
690
691 remote_tags.append(&mut local_tags);
714
715 let mut options = CollatorOptions::new();
716 options.strength = Some(Strength::Secondary);
717 options.numeric = Some(Numeric::On);
718
719 let collator = Collator::try_new(&Default::default(), options).unwrap();
720
721 remote_tags.sort_by(|a, b| {
722 let tag_a = a.tag.replace("refs/tags/", "");
723 let tag_b = b.tag.replace("refs/tags/", "");
724
725 collator.compare(&tag_b, &tag_a)
726 });
727
728 let package_tag = format!("{}@{}", package_info.name, package_info.version);
729
730 let mut match_tag = remote_tags.iter().find(|item| {
731 let tag = item.tag.replace("refs/tags/", "");
732 let matches: Vec<&str> = tag.matches(&package_tag).collect();
733
734 if matches.len() > 0 {
735 return true;
736 } else {
737 return false;
738 }
739 });
740
741 if match_tag.is_none() {
742 let mut highest_tag = None;
743
744 remote_tags.iter().for_each(|item| {
745 let tag = &item.tag.replace("refs/tags/", "");
746
747 if tag.contains(&package_info.name) {
748 if highest_tag.is_none() {
749 highest_tag = Some(String::from(tag));
750 }
751
752 let high_tag = highest_tag.as_ref().unwrap();
753 let current_tag_meta = package_scope_name_version(tag).unwrap();
754 let highest_tag_meta = package_scope_name_version(high_tag).unwrap();
755
756 let current_version = Version::from(¤t_tag_meta.version).unwrap();
757 let highest_version = Version::from(&highest_tag_meta.version).unwrap();
758
759 if current_version.compare_to(&highest_version, Cmp::Gt) {
760 highest_tag = Some(String::from(tag));
761 }
762 }
763 });
764
765 if highest_tag.is_some() {
766 let highest_tag = highest_tag.unwrap();
767 let highest_tag_meta = package_scope_name_version(&highest_tag).unwrap();
768
769 match_tag = remote_tags.iter().find(|item| {
770 let tag = item.tag.replace("refs/tags/", "");
771 let matches: Vec<&str> = tag.matches(&highest_tag_meta.full).collect();
772
773 if matches.len() > 0 {
774 return true;
775 } else {
776 return false;
777 }
778 });
779 }
780 }
781
782 if match_tag.is_some() {
783 let hash = &match_tag.unwrap().hash;
784 let tag = &match_tag.unwrap().tag;
785 let package = &package_info.name;
786
787 return Some(PublishTagInfo {
788 hash: hash.to_string(),
789 tag: tag.to_string(),
790 package: package.to_string(),
791 });
792 }
793
794 None
795}
796
797pub fn get_last_known_publish_tag_info_for_all_packages(
799 package_info: &Vec<PackageInfo>,
800 cwd: Option<String>,
801) -> Vec<Option<PublishTagInfo>> {
802 let root = match cwd {
803 Some(dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
804 None => get_project_root_path(None).unwrap(),
805 };
806
807 git_fetch_all(Some(root.to_string()), Some(true)).expect("Fetch all tags");
808
809 package_info
810 .iter()
811 .map(|item| get_last_known_publish_tag_info_for_package(&item, Some(root.to_string())))
812 .filter(|item| item.is_some())
813 .collect::<Vec<Option<PublishTagInfo>>>()
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819 use crate::{
820 manager::PackageManager, paths::get_project_root_path, utils::create_test_monorepo,
821 };
822 use std::fs::{remove_dir_all, File};
823
824 #[test]
825 fn test_git_fetch_all() -> Result<(), std::io::Error> {
826 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
827 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
828
829 let result = git_fetch_all(project_root, None)?;
830 assert_eq!(result, false);
831 remove_dir_all(&monorepo_dir)?;
832 Ok(())
833 }
834
835 #[test]
836 fn test_get_diverged_commit() -> Result<(), std::io::Error> {
837 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
838 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
839
840 let result = get_diverged_commit(String::from("@scope/package-a@1.0.0"), project_root);
841
842 assert!(result.is_some());
843 remove_dir_all(&monorepo_dir)?;
844 Ok(())
845 }
846
847 #[test]
848 fn test_git_current_sha() -> Result<(), std::io::Error> {
849 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
850 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
851
852 let result = git_current_sha(project_root);
853 assert_eq!(result.is_empty(), false);
854 remove_dir_all(&monorepo_dir)?;
855 Ok(())
856 }
857
858 #[test]
859 fn test_git_previous_sha() -> Result<(), std::io::Error> {
860 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
861 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
862
863 let result = git_previous_sha(project_root);
864 assert_eq!(result.is_empty(), true);
865 remove_dir_all(&monorepo_dir)?;
866 Ok(())
867 }
868
869 #[test]
870 fn test_git_workdir_unclean() -> Result<(), std::io::Error> {
871 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
872 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
873 let js_path = monorepo_dir.join("packages/package-a/index.js");
874
875 let mut js_file = File::create(&js_path)?;
876 js_file.write_all(r#"export const message = "hello";"#.as_bytes())?;
877
878 let result = git_workdir_unclean(project_root);
879 assert_eq!(result, true);
880 remove_dir_all(&monorepo_dir)?;
881 Ok(())
882 }
883
884 #[test]
885 fn test_git_branch_from_commit() -> Result<(), std::io::Error> {
886 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
887 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
888
889 let commit = git_current_sha(Some(project_root.as_ref().unwrap().to_string()));
890 let result = git_branch_from_commit(commit, project_root);
891 assert_eq!(result.is_some(), true);
892 remove_dir_all(&monorepo_dir)?;
893 Ok(())
894 }
895
896 #[test]
897 fn test_get_commits_since() -> Result<(), std::io::Error> {
898 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
899 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
900
901 let result = get_commits_since(
902 project_root,
903 Some(String::from("main")),
904 Some(String::from("packages/package-a")),
905 );
906 let count = result.len();
907
908 assert_eq!(count, 0);
909 remove_dir_all(&monorepo_dir)?;
910 Ok(())
911 }
912
913 #[test]
914 fn test_get_local_tags() -> Result<(), std::io::Error> {
915 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
916 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
917
918 let result = get_remote_or_local_tags(project_root, Some(true));
919 let count = result.len();
920
921 assert_eq!(count, 3);
922 remove_dir_all(&monorepo_dir)?;
923 Ok(())
924 }
925
926 #[test]
927 fn test_git_all_files_changed_since_sha() -> Result<(), std::io::Error> {
928 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
929 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
930
931 let result = git_all_files_changed_since_sha(String::from("main"), project_root);
932 let count = result.len();
933
934 assert_eq!(count, 0);
935 remove_dir_all(&monorepo_dir)?;
936 Ok(())
937 }
938}