add bootstrapping.md
This commit is contained in:
Generated
+3
@@ -4,6 +4,9 @@
|
|||||||
<execution />
|
<execution />
|
||||||
</component>
|
</component>
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
<component name="NodePackageJsonFileManager">
|
<component name="NodePackageJsonFileManager">
|
||||||
<packageJsonPaths />
|
<packageJsonPaths />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ Jeder Build erhält einen eigenen temporären Worktree.
|
|||||||
|
|
||||||
1. Eingebaute Defaults
|
1. Eingebaute Defaults
|
||||||
2. Globale Server-Konfiguration
|
2. Globale Server-Konfiguration
|
||||||
3. Repository-Installation (.git/gittally/config.yml)
|
3. Repository-Installation (.git/gittally/.gittally.yml)
|
||||||
4. Projektkonfiguration (.gittally.yml)
|
4. Projektkonfiguration (.gittally.yml)
|
||||||
5. Branchprofile
|
5. Branchprofile
|
||||||
|
|
||||||
@@ -143,20 +143,20 @@ Jeder Build erhält einen eigenen temporären Worktree.
|
|||||||
### Initialisierung
|
### Initialisierung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar gittally.jar init
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar init
|
||||||
```
|
```
|
||||||
|
|
||||||
### Serverstart
|
### Serverstart
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar gittally.jar server
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar server
|
||||||
```
|
```
|
||||||
|
|
||||||
### Konfigurationsanzeige
|
### Konfigurationsanzeige
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar gittally.jar config:print
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar config:print
|
||||||
java -jar gittally.jar config:print --full
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar config:print --full
|
||||||
```
|
```
|
||||||
|
|
||||||
## Erweiterungen
|
## Erweiterungen
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# GitTally Bootstrapping
|
||||||
|
|
||||||
|
Bootstrapping prepares a git repository for use with GitTally.
|
||||||
|
It creates the config files described in [configuration.md](configuration.md) and optionally installs GitTally as a system service.
|
||||||
|
|
||||||
|
Run `init` once per repository, from within a checked-out working tree.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Git repository with at least one commit
|
||||||
|
- A remote named `origin` (used for auto-detection)
|
||||||
|
- Java runtime available (JRE 21)
|
||||||
|
|
||||||
|
## Running `init`
|
||||||
|
|
||||||
|
First, in `<gittally-root>`, build the application to generate the executable JAR file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `init` using the generated JAR (not the `-plain.jar`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -jar <gittally-root>/build/libs/gittally-0.1.0-SNAPSHOT.jar init
|
||||||
|
```
|
||||||
|
|
||||||
|
`init` performs the following steps in order:
|
||||||
|
|
||||||
|
### 1. Detect the Repository Root
|
||||||
|
|
||||||
|
GitTally resolves the repository root by running `git rev-parse --show-toplevel`.
|
||||||
|
If the current directory is not inside a git repository, `init` exits with an error.
|
||||||
|
|
||||||
|
### 2. Auto-detect Gitea Connection from `origin`
|
||||||
|
|
||||||
|
If `gitea.baseUrl`, `gitea.owner`, and `gitea.repo` are already set in `.gittally.yml`, these values are used.
|
||||||
|
|
||||||
|
Otherwise, GitTally inspects the `origin` remote URL and derives the Gitea connection defaults:
|
||||||
|
|
||||||
|
| Origin URL form | Detected values |
|
||||||
|
|--------------------------------------------|----------------------------------------|
|
||||||
|
| `https://git.example.org/my-org/my-repo` | baseUrl, owner, repo |
|
||||||
|
| `git@git.example.org:my-org/my-repo.git` | baseUrl, owner, repo |
|
||||||
|
|
||||||
|
The `.git` suffix is stripped from the repo name. The username embedded in HTTPS URLs
|
||||||
|
(e.g. `https://user@git.example.org/…`) is used as the default `git.account`.
|
||||||
|
|
||||||
|
- **`gitea.owner`**: The Gitea user or organization owning the repository. Used for Gitea API operations, such as reporting build status checks.
|
||||||
|
- **`git.account`**: The technical username used for git HTTPS authentication.
|
||||||
|
|
||||||
|
### 3. Create the Repo-Install Config
|
||||||
|
|
||||||
|
Creates `.git/gittally/.gittally.yml` (and its parent directory if needed).
|
||||||
|
This file is **never committed** to the repository and is used for all branches,
|
||||||
|
as long as not overridden by a project config.
|
||||||
|
|
||||||
|
If the file already exists, `init` prints a notice and leaves it untouched.
|
||||||
|
|
||||||
|
The generated file contains the machine-local secrets with auto-detected values pre-filled:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
git:
|
||||||
|
account: <detected-or-placeholder>
|
||||||
|
token: # paste your Gitea API token here
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create the Branch/Project Config
|
||||||
|
|
||||||
|
Creates `.gittally.yml` in the repository root with project-level defaults.
|
||||||
|
|
||||||
|
If the file already exists, `init` prints a notice and leaves it untouched.
|
||||||
|
|
||||||
|
The generated file is a commented template based on the defaults documented in
|
||||||
|
[configuration.md](configuration.md) and includes the auto-detected Gitea settings:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
gitea:
|
||||||
|
baseUrl: <detected-or-placeholder>
|
||||||
|
owner: <detected-or-placeholder>
|
||||||
|
repo: <detected-or-placeholder>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you have to configure *gitTally* by amending this config file according to [configuration.md](configuration.md).
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
`init` prints one line per action taken:
|
||||||
|
|
||||||
|
```
|
||||||
|
created .git/gittally/.gittally.yml
|
||||||
|
created .gittally.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, when files already exist:
|
||||||
|
|
||||||
|
```
|
||||||
|
.git/gittally/.gittally.yml already exists — not overwritten
|
||||||
|
.gittally.yml already exists — not overwritten
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future: Docker-based Deployment
|
||||||
|
|
||||||
|
GitTally is intended to run on Hostsharing Container Server environments, which provide Docker
|
||||||
|
but no Java runtime. A later development step will add a Docker image distribution where:
|
||||||
|
|
||||||
|
- GitTally itself runs as a Docker container (image bundles the JRE + JAR)
|
||||||
|
- Builds are spawned by mounting the host Docker socket (`/var/run/docker.sock`)
|
||||||
|
- `init` then optionally generates a `docker-compose.yml`, a secrets env file, and a systemd unit
|
||||||
|
that starts the Compose stack at boot
|
||||||
|
|
||||||
|
Until then, a Java runtime must be available on the host.
|
||||||
|
|
||||||
|
## Next Steps After `init`
|
||||||
|
|
||||||
|
1. Open `.git/gittally/.gittally.yml` and set `git.token` and `git.account`.
|
||||||
|
2. Review `.gittally.yml` and add/adjust any branch build settings.
|
||||||
|
3. Verify the effective configuration:
|
||||||
|
```bash
|
||||||
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar config:print --full
|
||||||
|
```
|
||||||
|
4. Start the server:
|
||||||
|
```bash
|
||||||
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar server
|
||||||
|
```
|
||||||
@@ -7,15 +7,15 @@ GitTally is configured via YAML files. Settings are merged from two sources in o
|
|||||||
| Layer | Path | Committed to Git | Purpose |
|
| Layer | Path | Committed to Git | Purpose |
|
||||||
|--------------------------|----------------------------|------------------|----------------------------------------------|
|
|--------------------------|----------------------------|------------------|----------------------------------------------|
|
||||||
| Project config | `.gittally.yml` | Yes | Shared team settings |
|
| Project config | `.gittally.yml` | Yes | Shared team settings |
|
||||||
| Repo installation config | `.git/gittally/config.yml` | No | Machine- or user-specific overrides, secrets |
|
| Repo installation config | `.git/gittally/.gittally.yml` | No | Machine- or user-specific overrides, secrets |
|
||||||
|
|
||||||
The repo install config (`.git/gittally/config.yml`) wins on any key present in both files. Typically used to set `gitea.token` without committing it.
|
The repo install config (`.git/gittally/.gittally.yml`) wins on any key present in both files. Typically used to set `git.token` and `git.account` without committing them.
|
||||||
|
|
||||||
## Inspect the Effective Config
|
## Inspect the Effective Config
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar gittally.jar config:print # only explicitly set values
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar config:print # only explicitly set values
|
||||||
java -jar gittally.jar config:print --full # all values including defaults
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar config:print --full # all values including defaults
|
||||||
```
|
```
|
||||||
|
|
||||||
## `.gittally.yml`
|
## `.gittally.yml`
|
||||||
@@ -30,7 +30,7 @@ server:
|
|||||||
# Gitea integration for fetching commits and posting build statuses.
|
# Gitea integration for fetching commits and posting build statuses.
|
||||||
gitea:
|
gitea:
|
||||||
baseUrl: https://git.example.org # base URL of the Gitea instance
|
baseUrl: https://git.example.org # base URL of the Gitea instance
|
||||||
owner: my-org # repository owner (user or organisation)
|
owner: my-org # repository owner (user or organisation) for Gitea API (e.g. status checks)
|
||||||
repo: my-repo # repository name
|
repo: my-repo # repository name
|
||||||
statusContext: GitTally # label shown on Gitea commit status checks (default: GitTally)
|
statusContext: GitTally # label shown on Gitea commit status checks (default: GitTally)
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ branches:
|
|||||||
- "04:00"
|
- "04:00"
|
||||||
```
|
```
|
||||||
|
|
||||||
## `.git/gittally/config.yml` (not committed)
|
## `.git/gittally/.gittally.yml` (not committed)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Machine- or user-specific overrides. Keys here win over .gittally.yml.
|
# Machine- or user-specific overrides and secrets. Keys here win over .gittally.yml.
|
||||||
gitea:
|
git:
|
||||||
gitUsername: my-user # git username for HTTPS authentication
|
account: my-user # technical username for git HTTPS authentication
|
||||||
token: glpat-xxxxxxxxxxxxxxxxxxxx # Gitea API token — never commit this
|
token: glpat-xxxxxxxxxxxxxxxxxxxx # Gitea API token — never commit this
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package de.hoennig.gittally.commands
|
package de.hoennig.gittally.commands
|
||||||
|
|
||||||
|
import de.hoennig.gittally.git.GitService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Command(
|
@Command(
|
||||||
@@ -9,8 +12,141 @@ import picocli.CommandLine.Command
|
|||||||
description = ["Initialize GitTally for the current repository"],
|
description = ["Initialize GitTally for the current repository"],
|
||||||
mixinStandardHelpOptions = true,
|
mixinStandardHelpOptions = true,
|
||||||
)
|
)
|
||||||
class InitCommand : Runnable {
|
class InitCommand(
|
||||||
|
private val gitService: GitService,
|
||||||
|
) : Runnable {
|
||||||
|
var workingDir: Path = Paths.get(".")
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
println("init – not yet implemented")
|
val normalizedWorkingDir = workingDir.toAbsolutePath().normalize()
|
||||||
|
val root =
|
||||||
|
try {
|
||||||
|
gitService.getTopLevel(normalizedWorkingDir)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Error: ${e.message}")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val originUrl = gitService.getOriginUrl(root)
|
||||||
|
val detected = detectFromUrl(originUrl)
|
||||||
|
|
||||||
|
createRepoInstallConfig(root, detected, normalizedWorkingDir)
|
||||||
|
createProjectConfig(root, detected, normalizedWorkingDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detectFromUrl(url: String?): DetectedValues {
|
||||||
|
if (url == null) return DetectedValues()
|
||||||
|
|
||||||
|
if (url.startsWith("http")) {
|
||||||
|
val regex = Regex("""https?://(?:([^@]+)@)?([^/]+)/([^/]+)/([^/.]+)(?:\.git)?""")
|
||||||
|
val match = regex.find(url)
|
||||||
|
if (match != null) {
|
||||||
|
val (user, host, owner, repo) = match.destructured
|
||||||
|
return DetectedValues(
|
||||||
|
baseUrl = "https://$host",
|
||||||
|
owner = owner,
|
||||||
|
repo = repo,
|
||||||
|
account = user,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (url.contains("@") && url.contains(":")) {
|
||||||
|
// Assume SSH: git@host:owner/repo.git
|
||||||
|
val regex = Regex("""([^@]+)@([^:]+):([^/]+)/([^/.]+)(?:\.git)?""")
|
||||||
|
val match = regex.find(url)
|
||||||
|
if (match != null) {
|
||||||
|
val (_, host, owner, repo) = match.destructured
|
||||||
|
return DetectedValues(
|
||||||
|
baseUrl = "https://$host",
|
||||||
|
owner = owner,
|
||||||
|
repo = repo,
|
||||||
|
account = "", // SSH user 'git' is not the account name we want for HTTPS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DetectedValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRepoInstallConfig(
|
||||||
|
root: Path,
|
||||||
|
detected: DetectedValues,
|
||||||
|
normalizedWorkingDir: Path,
|
||||||
|
) {
|
||||||
|
val file = root.resolve(".git/gittally/.gittally.yml")
|
||||||
|
if (file.toFile().exists()) {
|
||||||
|
println("${file.toFile().relativeTo(normalizedWorkingDir.toFile())} already exists — not overwritten")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file.parent.toFile().mkdirs()
|
||||||
|
val content =
|
||||||
|
"""
|
||||||
|
# Machine- or user-specific overrides and secrets. Keys here win over .gittally.yml.
|
||||||
|
git:
|
||||||
|
account: "${detected.account}" # technical username for git HTTPS authentication
|
||||||
|
token: "" # Gitea API token — never commit this
|
||||||
|
""".trimIndent()
|
||||||
|
file.toFile().writeText(content + "\n")
|
||||||
|
println("created ${file.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createProjectConfig(
|
||||||
|
root: Path,
|
||||||
|
detected: DetectedValues,
|
||||||
|
normalizedWorkingDir: Path,
|
||||||
|
) {
|
||||||
|
val file = root.resolve(".gittally.yml")
|
||||||
|
if (file.toFile().exists()) {
|
||||||
|
println("${file.toFile().relativeTo(normalizedWorkingDir.toFile())} already exists — not overwritten")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val content =
|
||||||
|
"""
|
||||||
|
server:
|
||||||
|
# Public base URL of this GitTally installation — used for all links posted to Gitea.
|
||||||
|
publicBaseUrl: ""
|
||||||
|
|
||||||
|
# Gitea integration for fetching commits and posting build statuses.
|
||||||
|
gitea:
|
||||||
|
baseUrl: ${detected.baseUrl} # base URL of the Gitea instance
|
||||||
|
owner: ${detected.owner} # repository owner (user or organisation) for Gitea API (e.g. status checks)
|
||||||
|
repo: ${detected.repo} # repository name
|
||||||
|
statusContext: GitTally # label shown on Gitea commit status checks (default: GitTally)
|
||||||
|
|
||||||
|
# Build artifact retention.
|
||||||
|
artifacts:
|
||||||
|
# number of builds to keep per branch
|
||||||
|
retentionPerBranch: 3
|
||||||
|
|
||||||
|
# Controls the branch-polling loop.
|
||||||
|
watcher:
|
||||||
|
# max commit age for new origin branches to be pulled automatically
|
||||||
|
newBranchMaxAge: 5d
|
||||||
|
|
||||||
|
# Per-branch build configuration.
|
||||||
|
# Use "default" as the fallback for all branches not listed explicitly.
|
||||||
|
branches:
|
||||||
|
default:
|
||||||
|
# run before each build
|
||||||
|
cleanCommand: rm -rf build
|
||||||
|
# shell command for each build
|
||||||
|
buildCommand: ./gradlew --console=plain --no-daemon test
|
||||||
|
# directories copied as build artifacts
|
||||||
|
artifactDirs:
|
||||||
|
- build/reports
|
||||||
|
stdoutLog: build.stdout.log # filename for captured stdout
|
||||||
|
stderrLog: build.stderr.log # filename for captured stderr
|
||||||
|
autoBuild:
|
||||||
|
enabled: false # whether to rebuild on schedule
|
||||||
|
times: ["01:00"] # UTC times HH:MM for scheduled builds
|
||||||
|
""".trimIndent()
|
||||||
|
file.toFile().writeText(content + "\n")
|
||||||
|
println("created ${file.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class DetectedValues(
|
||||||
|
val baseUrl: String = "",
|
||||||
|
val owner: String = "",
|
||||||
|
val repo: String = "",
|
||||||
|
val account: String = "",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class ConfigLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadRaw(workingDir: Path = Paths.get(".")): Map<String, Any?> {
|
fun loadRaw(workingDir: Path = Paths.get(".")): Map<String, Any?> {
|
||||||
val repoInstall = loadFile(workingDir.resolve(".git/gittally/config.yml").toFile())
|
val repoInstall = loadFile(workingDir.resolve(".git/gittally/.gittally.yml").toFile())
|
||||||
val project = loadFile(workingDir.resolve(".gittally.yml").toFile())
|
val project = loadFile(workingDir.resolve(".gittally.yml").toFile())
|
||||||
return deepMerge(project, repoInstall)
|
return deepMerge(project, repoInstall)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.hoennig.gittally.config
|
|||||||
|
|
||||||
data class GitTallyConfig(
|
data class GitTallyConfig(
|
||||||
val server: ServerConfig = ServerConfig(),
|
val server: ServerConfig = ServerConfig(),
|
||||||
|
val git: GitConfig = GitConfig(),
|
||||||
val gitea: GiteaConfig = GiteaConfig(),
|
val gitea: GiteaConfig = GiteaConfig(),
|
||||||
val artifacts: ArtifactsConfig = ArtifactsConfig(),
|
val artifacts: ArtifactsConfig = ArtifactsConfig(),
|
||||||
val watcher: WatcherConfig = WatcherConfig(),
|
val watcher: WatcherConfig = WatcherConfig(),
|
||||||
@@ -12,12 +13,15 @@ data class ServerConfig(
|
|||||||
val publicBaseUrl: String = "",
|
val publicBaseUrl: String = "",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class GitConfig(
|
||||||
|
val account: String = "",
|
||||||
|
val token: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
data class GiteaConfig(
|
data class GiteaConfig(
|
||||||
val baseUrl: String = "",
|
val baseUrl: String = "",
|
||||||
val owner: String = "",
|
val owner: String = "",
|
||||||
val repo: String = "",
|
val repo: String = "",
|
||||||
val gitUsername: String = "",
|
|
||||||
val token: String = "",
|
|
||||||
val statusContext: String = "GitTally",
|
val statusContext: String = "GitTally",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.hoennig.gittally.git
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class GitService {
|
||||||
|
fun getTopLevel(workingDir: Path = Paths.get(".")): Path {
|
||||||
|
val process =
|
||||||
|
ProcessBuilder("git", "rev-parse", "--show-toplevel")
|
||||||
|
.directory(workingDir.toFile())
|
||||||
|
.start()
|
||||||
|
if (process.waitFor() != 0) {
|
||||||
|
throw RuntimeException("Not a git repository")
|
||||||
|
}
|
||||||
|
return Paths.get(
|
||||||
|
process.inputStream
|
||||||
|
.bufferedReader()
|
||||||
|
.readText()
|
||||||
|
.trim(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOriginUrl(workingDir: Path = Paths.get(".")): String? {
|
||||||
|
val process =
|
||||||
|
ProcessBuilder("git", "remote", "get-url", "origin")
|
||||||
|
.directory(workingDir.toFile())
|
||||||
|
.start()
|
||||||
|
if (process.waitFor() != 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return process.inputStream
|
||||||
|
.bufferedReader()
|
||||||
|
.readText()
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package de.hoennig.gittally.commands
|
||||||
|
|
||||||
|
import de.hoennig.gittally.git.GitService
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.file.shouldExist
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.kotest.matchers.string.shouldContain
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
class InitCommandTest : FunSpec() {
|
||||||
|
private val gitService = mockk<GitService>()
|
||||||
|
private val initCommand = InitCommand(gitService)
|
||||||
|
|
||||||
|
init {
|
||||||
|
test("creates config files with auto-detected values") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
val projectConfig = tempDir.resolve(".gittally.yml")
|
||||||
|
projectConfig.toFile().shouldExist()
|
||||||
|
val projectContent = projectConfig.toFile().readText()
|
||||||
|
projectContent shouldContain "baseUrl: https://git.example.org"
|
||||||
|
projectContent shouldContain "owner: my-org"
|
||||||
|
projectContent shouldContain "repo: my-repo"
|
||||||
|
|
||||||
|
val repoConfig = tempDir.resolve(".git/gittally/.gittally.yml")
|
||||||
|
repoConfig.toFile().shouldExist()
|
||||||
|
val repoContent = repoConfig.toFile().readText()
|
||||||
|
repoContent shouldContain "account: \"\"" // no user in https URL
|
||||||
|
}
|
||||||
|
|
||||||
|
test("detects account from https url") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "https://ci-user@git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
val repoConfig = tempDir.resolve(".git/gittally/.gittally.yml")
|
||||||
|
val repoContent = repoConfig.toFile().readText()
|
||||||
|
repoContent shouldContain "account: \"ci-user\""
|
||||||
|
}
|
||||||
|
|
||||||
|
test("parses ssh url") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "git@git.example.org:my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
val projectConfig = tempDir.resolve(".gittally.yml")
|
||||||
|
val projectContent = projectConfig.toFile().readText()
|
||||||
|
projectContent shouldContain "baseUrl: https://git.example.org" // fallback to https
|
||||||
|
projectContent shouldContain "owner: my-org"
|
||||||
|
projectContent shouldContain "repo: my-repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
test("does not overwrite existing files") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
|
||||||
|
val projectConfig = tempDir.resolve(".gittally.yml")
|
||||||
|
projectConfig.toFile().writeText("existing: content")
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
projectConfig.toFile().readText() shouldBe "existing: content"
|
||||||
|
}
|
||||||
|
|
||||||
|
test("reproduces path root mismatch issue") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test").toAbsolutePath().normalize()
|
||||||
|
initCommand.workingDir = Paths.get(".") // Set to relative path as in real app
|
||||||
|
|
||||||
|
// We need to mock getTopLevel to return the absolute path
|
||||||
|
every { gitService.getTopLevel(any()) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(any()) } returns "https://git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
// This should not throw IllegalArgumentException
|
||||||
|
initCommand.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,6 @@ class ConfigLoaderTest : FunSpec() {
|
|||||||
val config = loader.load(dir)
|
val config = loader.load(dir)
|
||||||
config.gitea.owner shouldBe "my-org"
|
config.gitea.owner shouldBe "my-org"
|
||||||
config.gitea.repo shouldBe "my-repo"
|
config.gitea.repo shouldBe "my-repo"
|
||||||
config.branches["default"]!!.buildCommand shouldBe BranchConfig().buildCommand
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("repo install config overrides project config for same keys") {
|
test("repo install config overrides project config for same keys") {
|
||||||
@@ -40,20 +39,23 @@ class ConfigLoaderTest : FunSpec() {
|
|||||||
dir.resolve(".gittally.yml").toFile().writeText(
|
dir.resolve(".gittally.yml").toFile().writeText(
|
||||||
"""
|
"""
|
||||||
gitea:
|
gitea:
|
||||||
owner: my-org
|
owner: original-org
|
||||||
|
git:
|
||||||
token: from-project
|
token: from-project
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
dir.resolve(".git/gittally").toFile().mkdirs()
|
dir.resolve(".git/gittally").toFile().mkdirs()
|
||||||
dir.resolve(".git/gittally/config.yml").toFile().writeText(
|
dir.resolve(".git/gittally/.gittally.yml").toFile().writeText(
|
||||||
"""
|
"""
|
||||||
gitea:
|
gitea:
|
||||||
|
owner: override-org
|
||||||
|
git:
|
||||||
token: from-repo-install
|
token: from-repo-install
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
val config = loader.load(dir)
|
val config = loader.load(dir)
|
||||||
config.gitea.owner shouldBe "my-org"
|
config.gitea.owner shouldBe "override-org"
|
||||||
config.gitea.token shouldBe "from-repo-install"
|
config.git.token shouldBe "from-repo-install"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("loadRaw only returns explicitly set values") {
|
test("loadRaw only returns explicitly set values") {
|
||||||
@@ -108,6 +110,7 @@ class ConfigLoaderTest : FunSpec() {
|
|||||||
test("toYaml serializes GitTallyConfig with all sections") {
|
test("toYaml serializes GitTallyConfig with all sections") {
|
||||||
val yaml = loader.toYaml(GitTallyConfig())
|
val yaml = loader.toYaml(GitTallyConfig())
|
||||||
yaml shouldContain "server:"
|
yaml shouldContain "server:"
|
||||||
|
yaml shouldContain "git:"
|
||||||
yaml shouldContain "gitea:"
|
yaml shouldContain "gitea:"
|
||||||
yaml shouldContain "artifacts:"
|
yaml shouldContain "artifacts:"
|
||||||
yaml shouldContain "watcher:"
|
yaml shouldContain "watcher:"
|
||||||
|
|||||||
Reference in New Issue
Block a user