michal/tit
Browse tree · Show commit · Download archive
Diff
3e34f4c1d68e → 82a09578b0e3
src/http/mod.rs
Mode 100644 → 100644; object f3af1d63554f → 67e654a01d9a
@@ -946,7 +946,7 @@
headers: HeaderMap,
body: Bytes,
) -> Response {
- let fields = match parse_named_form(&headers, &body, &["csrf", "name", "object-format"]) {
+ let fields = match parse_named_form(&headers, &body, &["csrf", "name"]) {
Ok(fields) => fields,
Err(()) => {
return repository_form_error(
@@ -969,17 +969,6 @@
"The request is not authorized.",
);
}
- let object_format = match fields[2].as_str() {
- "sha1" => gix::hash::Kind::Sha1,
- "sha256" => gix::hash::Kind::Sha256,
- _ => {
- return repository_form_error(
- &request_id.0,
- StatusCode::BAD_REQUEST,
- "The repository request is not valid.",
- );
- }
- };
let csrf_for_auth = csrf.clone();
let session = match login_job(state.clone(), move |login| {
login.authenticate(&session_token, Some(&csrf_for_auth))
@@ -998,7 +987,7 @@
repositories.create_for_account(
&owner_for_job,
&slug_for_job,
- object_format,
+ gix::hash::Kind::Sha1,
&correlation_id,
)
})
src/http/public.rs
Mode 100644 → 100644; object 5a8c593a84f9 → dcb4b473bfa6
@@ -36,7 +36,7 @@
use super::{PublicWebConfig, RequestActor, RequestId, WebState, render_error};
const MAX_HISTORY_COMMITS: usize = 10_000;
-const MAX_SUMMARY_COMMITS: usize = 50;
+const MAX_SUMMARY_COMMITS: usize = 10;
const MAX_SEARCH_QUERY_BYTES: usize = 256;
#[derive(Clone)]
@@ -272,6 +272,7 @@
)
.route("/{owner}/{repository}/issues/rss.xml", get(issue_rss_feed))
.route("/{owner}/{repository}/search", get(search))
+ .route("/{owner}/{repository}/commits", get(commits))
.route("/{owner}/{repository}/commit/{commit}", get(commit))
.route("/{owner}/{repository}/diff/{old}/{new}", get(diff))
.route("/{owner}/{repository}/tree/{commit}", get(tree_root))
@@ -494,6 +495,34 @@
let cancellation = ReadCancellation::default();
let references = service.references(&cancellation)?;
Ok(RepositoryPage::refs(record, references))
+ })
+ .await;
+ render_page(result, &request_id.0, signed_in)
+}
+
+async fn commits(
+ State(state): State<WebState>,
+ Extension(request_id): Extension<RequestId>,
+ Extension(actor): Extension<RequestActor>,
+ AxumPath(path): AxumPath<RepositoryPath>,
+) -> Response {
+ let Some(web) = state.public else {
+ return route_error(RouteError::NotFound, &request_id.0);
+ };
+ let signed_in = actor.0.is_some();
+ let result = web
+ .read(actor.0, path.owner, path.repository, |record, service| {
+ let cancellation = ReadCancellation::default();
+ let references = service.references(&cancellation)?;
+ let head = references
+ .iter()
+ .find(|reference| reference.name == b"HEAD")
+ .map(|reference| reference.target);
+ let history = match head {
+ Some(head) => service.history(head, &cancellation)?,
+ None => Vec::new(),
+ };
+ Ok(RepositoryPage::commits(record, head, history))
})
.await;
render_page(result, &request_id.0, signed_in)
@@ -1248,7 +1277,6 @@
signed_in: bool,
owner: String,
repository: String,
- object_format: String,
created_at: i64,
page_title: String,
page_kind: &'static str,
@@ -1288,7 +1316,6 @@
signed_in: false,
owner: record.owner,
repository: record.slug,
- object_format: record.object_format,
created_at: record.created_at,
page_title: title,
page_kind,
@@ -1375,6 +1402,14 @@
}
})
.collect();
+ page
+ }
+
+ fn commits(record: RepositoryRecord, head: Option<ObjectId>, history: Vec<CommitInfo>) -> Self {
+ let mut page = Self::base(record, "commits", "Commits".to_owned());
+ page.has_head = head.is_some();
+ page.commit_id = head.map(|id| id.to_string()).unwrap_or_default();
+ page.history = history.into_iter().map(CommitView::from).collect();
page
}
src/ssh.rs
Mode 100644 → 100644; object 5af100db1f04 → fefa3ac234f6
@@ -37,8 +37,7 @@
const MAX_ISSUE_INPUT_BYTES: usize = MAX_TITLE_BYTES + 1 + MAX_BODY_BYTES;
const SSH_ATTEMPTS_PER_MINUTE: usize = 30;
const MAX_SSH_CLIENTS: usize = 4096;
-const REPOSITORY_CREATE_USAGE: &str =
- "repo create NAME [--object-format sha1|sha256] [--output human|json]";
+const REPOSITORY_CREATE_USAGE: &str = "repo create NAME [--output human|json]";
const ISSUE_LIST_USAGE: &str = "issue list OWNER/REPOSITORY [--output human|json]";
const ISSUE_CREATE_USAGE: &str = "issue create OWNER/REPOSITORY [--output human|json]";
const PULL_REQUEST_CHECKOUT_USAGE: &str =
@@ -47,7 +46,7 @@
Available tit SSH commands:
help
tit --version
- repo create NAME [--object-format sha1|sha256] [--output human|json]
+ repo create NAME [--output human|json]
issue list OWNER/REPOSITORY [--output human|json]
issue create OWNER/REPOSITORY [--output human|json]
pr checkout OWNER/REPOSITORY NUMBER [--output human|json]
@@ -945,7 +944,6 @@
struct RepositoryCreateCommand {
slug: String,
- object_format: gix::hash::Kind,
output: CommandOutput,
}
@@ -978,18 +976,10 @@
return Err(());
}
let slug = tokens.next().ok_or(())?.to_owned();
- let mut object_format = None;
let mut output = None;
while let Some(option) = tokens.next() {
let value = tokens.next().ok_or(())?;
match option {
- "--object-format" if object_format.is_none() => {
- object_format = Some(match value {
- "sha1" => gix::hash::Kind::Sha1,
- "sha256" => gix::hash::Kind::Sha256,
- _ => return Err(()),
- });
- }
"--output" if output.is_none() => {
output = Some(match value {
"human" => CommandOutput::Human,
@@ -1002,7 +992,6 @@
}
Ok(RepositoryCreateCommand {
slug,
- object_format: object_format.unwrap_or(gix::hash::Kind::Sha1),
output: output.unwrap_or(CommandOutput::Human),
})
}
@@ -1036,7 +1025,7 @@
.create_for_account(
&actor,
&command.slug,
- command.object_format,
+ gix::hash::Kind::Sha1,
&correlation_id,
)
.map(|repository| (repository, output))
@@ -1057,8 +1046,8 @@
session.data(
channel,
format!(
- "Created repository {}/{}.\nObject format: {}\n",
- repository.owner, repository.slug, repository.object_format
+ "Created repository {}/{}.\n",
+ repository.owner, repository.slug
)
.into_bytes(),
)?;
@@ -1068,8 +1057,8 @@
session.data(
channel,
format!(
- "{{\"version\":1,\"status\":\"success\",\"repository\":{{\"owner\":\"{}\",\"name\":\"{}\",\"object_format\":\"{}\"}}}}\n",
- repository.owner, repository.slug, repository.object_format
+ "{{\"version\":1,\"status\":\"success\",\"repository\":{{\"owner\":\"{}\",\"name\":\"{}\"}}}}\n",
+ repository.owner, repository.slug
)
.into_bytes(),
)?;
templates/account-page.html
Mode 100644 → 100644; object 5fa073504794 → 6054f0c54ba0
@@ -15,13 +15,6 @@
<label for="repository-name">Name</label>
<input id="repository-name" name="name" required maxlength="100" pattern="([a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9])">
</p>
- <p>
- <label for="object-format">Object format</label>
- <select id="object-format" name="object-format">
- <option value="sha1">SHA-1</option>
- <option value="sha256">SHA-256</option>
- </select>
- </p>
<button type="submit">Create repository</button>
</form>
<h2>Feeds</h2>
templates/repository.html
Mode 100644 → 100644; object c893168b491c → 07ffbcbc3203
@@ -16,7 +16,6 @@
<dl>
<dt>HTTP</dt><dd><code>{{ http_clone_url }}</code></dd>
<dt>SSH</dt><dd><code>{{ ssh_clone_url }}</code></dd>
- <dt>Object format</dt><dd>{{ object_format }}</dd>
<dt>Created</dt><dd><time>{{ created_at }}</time></dd>
</dl>
</section>
@@ -28,6 +27,7 @@
<li><a href="/{{ owner }}/{{ repository }}/commit/{{ item.id }}"><code>{{ item.id }}</code></a> {{ item.summary }} — {{ item.author_name }}</li>
{% endfor %}
</ol>
+ <p><a href="/{{ owner }}/{{ repository }}/commits">View all commits</a></p>
</section>
{% if has_readme %}
<section aria-labelledby="readme-heading">
@@ -39,6 +39,19 @@
{% endif %}
</section>
{% endif %}
+{% else %}
+ <p>This repository has no commits.</p>
+{% endif %}
+{% endif %}
+
+{% if page_kind == "commits" %}
+ <h2>All commits</h2>
+{% if has_head %}
+ <ol class="commit-list">
+{% for item in history %}
+ <li><a href="/{{ owner }}/{{ repository }}/commit/{{ item.id }}"><code>{{ item.id }}</code></a> {{ item.summary }} — {{ item.author_name }}</li>
+{% endfor %}
+ </ol>
{% else %}
<p>This repository has no commits.</p>
{% endif %}
tests/public_routes.rs
Mode 100644 → 100644; object 899df2e2e7d7 → a91b32708355
@@ -128,6 +128,27 @@
assert!(summary_text.contains("/alice/example/atom.xml"));
assert!(summary_text.contains("/alice/example/rss.xml"));
assert!(summary_text.contains("/alice/example/search"));
+ assert!(summary_text.contains("/alice/example/commits\">View all commits</a>"));
+ assert_eq!(
+ summary_text
+ .matches("<li><a href=\"/alice/example/commit/")
+ .count(),
+ 10
+ );
+ assert!(!summary_text.contains("Object format"));
+
+ let commits = request(server.address(), "GET", "/alice/example/commits", &[], &[]);
+ assert_eq!(commits.status, 200);
+ assert_html_policy(&commits);
+ assert_repository_navigation(&commits, "alice", "example");
+ assert!(commits.text().contains("<h2>All commits</h2>"));
+ assert_eq!(
+ commits
+ .text()
+ .matches("<li><a href=\"/alice/example/commit/")
+ .count(),
+ 12
+ );
let mut feed_entry_ids = Vec::new();
for (path, content_type) in [
(
@@ -250,6 +271,7 @@
let routes = [
"/alice/example/refs".to_owned(),
+ "/alice/example/commits".to_owned(),
"/alice/example/search".to_owned(),
format!("/alice/example/commit/{}", fixture.head),
format!("/alice/example/tree/{}", fixture.head),
@@ -1354,6 +1376,9 @@
.expect("write malformed UTF-8 content");
commit_all(&worktree, "first commit");
let parent = rev_parse(&worktree, "HEAD");
+ for index in 1..=10 {
+ commit_empty(&worktree, &format!("intermediate commit {index}"));
+ }
fs::write(
worktree.join("nested/file.txt"),
b"first line\nsecond line\n",
@@ -1441,6 +1466,17 @@
.arg("-C")
.arg(worktree)
.args(["commit", "-q", "-m", message])
+ .env("GIT_AUTHOR_NAME", "Fixture Author")
+ .env("GIT_AUTHOR_EMAIL", "fixture@example.test")
+ .env("GIT_COMMITTER_NAME", "Fixture Author")
+ .env("GIT_COMMITTER_EMAIL", "fixture@example.test"));
+}
+
+fn commit_empty(worktree: &Path, message: &str) {
+ run(Command::new("git")
+ .arg("-C")
+ .arg(worktree)
+ .args(["commit", "-q", "--allow-empty", "-m", message])
.env("GIT_AUTHOR_NAME", "Fixture Author")
.env("GIT_AUTHOR_EMAIL", "fixture@example.test")
.env("GIT_COMMITTER_NAME", "Fixture Author")
tests/serve.rs
Mode 100644 → 100644; object 60d47e121da0 → 548846ca83b9
@@ -231,6 +231,7 @@
assert!(!account.contains("<a href=\"/signup\">Create account</a>"));
assert!(!account.contains("<a href=\"/recover\">Recover account</a>"));
assert!(account.contains("action=\"/account/repositories\""));
+ assert!(!account.contains("object-format"));
for path in ["/login", "/signup", "/recover"] {
let response = http_get_with_headers(http, path, &[("Cookie", &cookies)]);
assert!(response.starts_with("HTTP/1.1 303"));
@@ -253,22 +254,14 @@
let rejected_repository = http_form_with_headers(
http,
"/account/repositories",
- &[
- ("csrf", &"0".repeat(64)),
- ("name", "web-created"),
- ("object-format", "sha1"),
- ],
+ &[("csrf", &"0".repeat(64)), ("name", "web-created")],
&[("Cookie", &cookies)],
);
assert!(rejected_repository.starts_with("HTTP/1.1 403"));
let created_repository = http_form_with_headers(
http,
"/account/repositories",
- &[
- ("csrf", csrf),
- ("name", "web-created"),
- ("object-format", "sha256"),
- ],
+ &[("csrf", csrf), ("name", "web-created")],
&[("Cookie", &cookies)],
);
assert!(created_repository.starts_with("HTTP/1.1 303"));
@@ -770,7 +763,7 @@
assert!(human.status.success());
assert_eq!(
String::from_utf8(human.stdout).expect("read human command output"),
- "Created repository bob/example.\nObject format: sha1\n"
+ "Created repository bob/example.\n"
);
assert!(human.stderr.is_empty());
assert!(ssh_clone_repository_succeeds(
@@ -885,20 +878,12 @@
let machine = ssh_exec(
ssh,
&private_key,
- &[
- "repo",
- "create",
- "hash-agile",
- "--object-format",
- "sha256",
- "--output",
- "json",
- ],
+ &["repo", "create", "hash-agile", "--output", "json"],
);
assert!(machine.status.success());
assert_eq!(
String::from_utf8(machine.stdout).expect("read machine command output"),
- "{\"version\":1,\"status\":\"success\",\"repository\":{\"owner\":\"alice\",\"name\":\"hash-agile\",\"object_format\":\"sha256\"}}\n"
+ "{\"version\":1,\"status\":\"success\",\"repository\":{\"owner\":\"alice\",\"name\":\"hash-agile\"}}\n"
);
assert!(machine.stderr.is_empty());
@@ -1080,7 +1065,7 @@
(
"alice".to_owned(),
"hash-agile".to_owned(),
- "sha256".to_owned()
+ "sha1".to_owned()
),
]
);
tests/ssh.rs
Mode 100644 → 100644; object 4393063a9fe4 → 3327afb21dda
@@ -103,6 +103,7 @@
let help_text = String::from_utf8(help.stdout).expect("read the help output");
assert!(help_text.contains("Available tit SSH commands:"));
assert!(help_text.contains("repo create NAME"));
+ assert!(!help_text.contains("object-format"));
assert!(help_text.contains("issue list OWNER/REPOSITORY"));
assert!(help_text.contains("pr checkout OWNER/REPOSITORY NUMBER"));