summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Wernick <justin@worthe-it.co.za>2023-08-11 22:04:19 +0200
committerJustin Wernick <justin@worthe-it.co.za>2023-08-15 21:40:35 +0200
commit64da82c661b2a28c7a258bd30c0084633d0e1896 (patch)
treee631e496b70f10d8414a3d1962797aa6354de918
parent2203a06532979e8c4772dcb134c109e1f308031a (diff)
Create group directories if they don't already exist
- With the correct ownership - With the correct permissions Fix #5
-rw-r--r--.woodpecker.yml2
-rw-r--r--CHANGELOG.md9
-rw-r--r--README.md3
-rw-r--r--src/git.rs53
-rw-r--r--src/lib.rs2
-rw-r--r--src/user_info.rs9
-rw-r--r--tests/cli.rs18
-rw-r--r--tests/cli_test_utils/context.rs8
8 files changed, 83 insertions, 21 deletions
diff --git a/.woodpecker.yml b/.woodpecker.yml
index cee339a..bf90627 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,4 +1,4 @@
-pipeline:
+steps:
test:
image: rust
environment: [CARGO_TERM_COLOR=always]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1035c4..334d087 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
license. This application is now dual licensed with the MIT license, as is
common with Rust crates.
+### Fix
+
+- When a repo is created in a directory which does not exist yet, the directory
+ is created with appropriate permissions. For example, when creating a personal
+ repo if `git/username` does not exist, it will be created with personal read,
+ write and execute access for the user only. If `git/group` does not exist, it
+ will be created owned by `group`, with group level read, write and execute
+ access, and the setgid flag set.
+
## [0.2.0] - 2023-07-17
### Added
diff --git a/README.md b/README.md
index adb7236..bdc02e9 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,8 @@ cargo install shackle-shell
Next, Shackle expects a specific directory structure. Specifically, personal git
repos will live in `~/git/your-username` and shared repos will live in
-`~/git/your-group`.
+`~/git/your-group`. If you create a repo that requires one of these group
+directories and it doesn't exist yet, Shackle will create it.
If you have many users on your server, then consider making `~/git` a symlink to
the actual shared location for your git repos. For example, on my repo, all git
diff --git a/src/git.rs b/src/git.rs
index 9fa3e78..ea55f2c 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -1,11 +1,12 @@
use crate::{
parser::{GitReceivePackArgs, GitUploadPackArgs},
- user_info::{get_user_groups, get_username},
+ user_info::{get_gid, get_user_groups, get_username},
ShackleError,
};
use git2::{ErrorCode, Repository, RepositoryInitMode, RepositoryInitOptions};
use std::{
fs,
+ os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process::Command,
};
@@ -75,24 +76,48 @@ pub fn init(
description: &Option<String>,
branch: &str,
) -> Result<GitInitResult, ShackleError> {
+ if let Some(group) = &group {
+ if !verify_user_is_in_group(group) {
+ return Err(ShackleError::InvalidGroup);
+ }
+ }
+
+ let git_prefix = git_dir_prefix();
+ let collection_dir = match group {
+ Some(group) => group_git_dir(group),
+ None => personal_git_dir()?,
+ };
+ let path = collection_dir.join(repo_name).with_extension("git");
+
+ if !git_prefix.is_dir() {
+ fs::create_dir(&git_prefix)?;
+ }
+
+ if !collection_dir.is_dir() {
+ fs::create_dir(&collection_dir)?;
+
+ if let Some(group) = group {
+ let gid = get_gid(&group).expect("User is in group but no group ID?");
+ nix::unistd::chown(&collection_dir, None, Some(gid))?;
+ }
+
+ let mut perms = collection_dir.metadata()?.permissions();
+ perms.set_mode(match group {
+ Some(_) => 0o2770,
+ None => 0o700,
+ });
+ fs::set_permissions(&collection_dir, perms)?;
+ }
+
let mut init_opts = RepositoryInitOptions::new();
init_opts
.bare(true)
- .mkdir(true)
+ .mkdir(false)
.no_reinit(true)
.initial_head(branch);
-
- let path = match group {
- Some(group) => {
- if !verify_user_is_in_group(group) {
- return Err(ShackleError::InvalidGroup);
- }
-
- init_opts.mode(RepositoryInitMode::SHARED_GROUP);
- group_git_dir(group).join(repo_name).with_extension("git")
- }
- None => personal_git_dir()?.join(repo_name).with_extension("git"),
- };
+ if group.is_some() {
+ init_opts.mode(RepositoryInitMode::SHARED_GROUP);
+ }
Repository::init_opts(&path, &init_opts)?;
if let Some(description) = description {
diff --git a/src/lib.rs b/src/lib.rs
index 163e41b..62c050a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -97,6 +97,8 @@ pub enum ShackleError {
#[error(transparent)]
IoError(#[from] io::Error),
#[error(transparent)]
+ NixError(#[from] nix::errno::Errno),
+ #[error(transparent)]
GitError(#[from] git2::Error),
#[error(transparent)]
ReadlineError(#[from] ReadlineError),
diff --git a/src/user_info.rs b/src/user_info.rs
index 7a42db9..a352adb 100644
--- a/src/user_info.rs
+++ b/src/user_info.rs
@@ -1,4 +1,4 @@
-use nix::unistd::{getgroups, getuid, Group, User};
+use nix::unistd::{getgroups, getuid, Gid, Group, User};
pub fn get_username() -> Option<String> {
let uid = getuid();
@@ -13,6 +13,13 @@ pub fn get_user_groups() -> Vec<String> {
.collect()
}
+pub fn get_gid(group_name: &str) -> Option<Gid> {
+ nix::unistd::Group::from_name(&group_name)
+ .ok()
+ .flatten()
+ .map(|group| group.gid)
+}
+
#[cfg(test)]
mod test {
use super::*;
diff --git a/tests/cli.rs b/tests/cli.rs
index 01c3347..34249c2 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -2,6 +2,7 @@ mod cli_test_utils;
use anyhow::Result;
use cli_test_utils::*;
+use std::os::unix::fs::MetadataExt;
const REPO_NAME: &str = "my-repository";
const REPO_NAME_2: &str = "my-other-repository";
@@ -83,6 +84,23 @@ fn can_init_a_new_shared_git_repo() -> Result<()> {
verify_repo_exists(&repo_dir);
verify_repo_config_value(&repo_dir, "core.sharedrepository", Some("1"));
+ let expected_gid = nix::unistd::Group::from_name(&group)
+ .unwrap()
+ .unwrap()
+ .gid
+ .as_raw();
+ let group_dir = repo_dir.parent().unwrap();
+ let group_dir_metadata = group_dir.metadata().unwrap();
+ assert_eq!(group_dir_metadata.gid(), expected_gid);
+ assert_eq!(
+ group_dir_metadata.mode(),
+ 0o42770,
+ "Mode is {:o}",
+ group_dir_metadata.mode()
+ );
+
+ assert_eq!(repo_dir.metadata().unwrap().gid(), expected_gid);
+
Ok(())
}
diff --git a/tests/cli_test_utils/context.rs b/tests/cli_test_utils/context.rs
index 2517040..2c61085 100644
--- a/tests/cli_test_utils/context.rs
+++ b/tests/cli_test_utils/context.rs
@@ -100,15 +100,15 @@ impl TestContext {
}
}
+pub fn arbitrary_user_group() -> String {
+ get_user_groups().into_iter().next().unwrap()
+}
+
pub fn personal_repo_path(repo_name: &str) -> String {
let username = get_username().unwrap();
format!("git/{username}/{repo_name}.git")
}
-pub fn arbitrary_user_group() -> String {
- get_user_groups().into_iter().next().unwrap()
-}
-
pub fn group_repo_path(group: &str, repo_name: &str) -> String {
format!("git/{group}/{repo_name}.git")
}