michal/tit

Browse tree · Show commit · Download archive

Diff

3e34f4c1d68e82a09578b0e3

src/http/mod.rs

Mode 100644100644; object f3af1d63554f67e654a01d9a

@@ -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 100644100644; object 5a8c593a84f9dcb4b473bfa6

@@ -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 100644100644; object 5af100db1f04fefa3ac234f6

@@ -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 100644100644; object 5fa0735047946054f0c54ba0

@@ -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 100644100644; object c893168b491c07ffbcbc3203

@@ -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 100644100644; object 899df2e2e7d7a91b32708355

@@ -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 100644100644; object 60d47e121da0548846ca83b9

@@ -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 100644100644; object 4393063a9fe43327afb21dda

@@ -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"));