Skip to content

Commit db383e7

Browse files
authored
Merge pull request #168 from unisoncomputing/cp/history-api
Add branch-history API
2 parents b0f042b + 8187533 commit db383e7

File tree

9 files changed

+284
-3
lines changed

9 files changed

+284
-3
lines changed

share-api/src/Share/Postgres/Causal/Queries.hs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ module Share.Postgres.Causal.Queries
2525
hashCausal,
2626
bestCommonAncestor,
2727
isFastForward,
28+
pagedCausalAncestors,
29+
CausalDepth,
30+
CausalHistoryCursor,
2831

2932
-- * Sync
3033
expectCausalEntity,
@@ -54,6 +57,7 @@ import Share.Postgres.Patches.Queries qualified as PatchQ
5457
import Share.Postgres.Serialization qualified as S
5558
import Share.Postgres.Sync.Conversions qualified as Cv
5659
import Share.Prelude
60+
import Share.Utils.API (Cursor (..), CursorDirection (..), Limit (..), Paged, guardPaged, pagedOn)
5761
import Share.Utils.Postgres (OrdBy, ordered)
5862
import Share.Web.Authorization.Types (RolePermission (..))
5963
import Share.Web.Errors (MissingExpectedEntity (MissingExpectedEntity))
@@ -985,3 +989,54 @@ isFastForward fromCausalId toCausalId = do
985989
WHERE history.causal_id = #{fromCausalId}
986990
);
987991
|]
992+
993+
type CausalHistoryCursor = (CausalHash, CausalDepth)
994+
995+
type CausalDepth = Int64
996+
997+
pagedCausalAncestors ::
998+
(QueryM m) =>
999+
CausalId ->
1000+
Limit ->
1001+
Maybe (Cursor CausalHistoryCursor) ->
1002+
m (Paged CausalHistoryCursor CausalHash)
1003+
pagedCausalAncestors rootCausalId limit mayCursor =
1004+
do
1005+
let (filter, ordering, maybeReverse) = case mayCursor of
1006+
Nothing -> (mempty, [sql| ORDER BY (h.causal_depth, h.causal_hash) DESC |], id)
1007+
Just (Cursor (hash, depth) Next) -> ([sql| WHERE (h.causal_depth, h.causal_hash) < (#{depth}, #{hash}) |], [sql| ORDER BY (h.causal_depth, h.causal_hash) DESC |], id)
1008+
Just (Cursor (hash, depth) Previous) -> ([sql| WHERE (h.causal_depth, h.causal_hash) > (#{depth}, #{hash}) |], [sql| ORDER BY (h.causal_depth , h.causal_hash) ASC |], reverse)
1009+
rawResults <-
1010+
queryListRows @(CausalHash, CausalDepth)
1011+
[sql|
1012+
WITH RECURSIVE history(causal_id, causal_hash, causal_depth) AS (
1013+
SELECT causal.id, causal.hash, cd.depth
1014+
FROM causals causal
1015+
JOIN causal_depth cd ON causal.id = cd.causal_id
1016+
WHERE causal.id = #{rootCausalId}
1017+
UNION
1018+
SELECT c.id, c.hash, cd.depth
1019+
FROM history h
1020+
JOIN causal_ancestors ca ON h.causal_id = ca.causal_id
1021+
JOIN causals c ON ca.ancestor_id = c.id
1022+
JOIN causal_depth cd ON c.id = cd.causal_id
1023+
) SELECT h.causal_hash, h.causal_depth
1024+
FROM history h
1025+
^{filter}
1026+
^{ordering}
1027+
LIMIT #{limit + 1}
1028+
|]
1029+
let hasPrevPage = case mayCursor of
1030+
Just (Cursor _ Previous) -> length rawResults > fromIntegral (getLimit limit)
1031+
Just (Cursor _ Next) -> True
1032+
Nothing -> False
1033+
let hasNextPage = case mayCursor of
1034+
Just (Cursor _ Next) -> length rawResults > fromIntegral (getLimit limit)
1035+
Nothing -> length rawResults > fromIntegral (getLimit limit)
1036+
Just (Cursor _ Previous) -> True
1037+
pure rawResults
1038+
<&> maybeReverse
1039+
<&> take (fromIntegral (getLimit limit))
1040+
<&> pagedOn id
1041+
<&> guardPaged hasPrevPage hasNextPage
1042+
<&> fmap fst

share-api/src/Share/Web/Share/Branches/API.hs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
module Share.Web.Share.Branches.API where
55

66
import Data.Time (UTCTime)
7+
import Servant
78
import Share.IDs
9+
import Share.Postgres.Causal.Queries (CausalHistoryCursor)
810
import Share.Utils.API
911
import Share.Utils.Caching
10-
import Share.Web.Share.Branches.Types (BranchKindFilter, ShareBranch)
12+
import Share.Web.Share.Branches.Types (BranchHistoryResponse, BranchKindFilter, ShareBranch)
1113
import Share.Web.Share.CodeBrowsing.API (CodeBrowseAPI)
1214
import Share.Web.Share.Types
13-
import Servant
1415
import U.Codebase.HashTags (CausalHash)
1516

1617
type ProjectBranchesAPI =
@@ -20,6 +21,7 @@ type ProjectBranchesAPI =
2021
type ProjectBranchResourceAPI =
2122
( ("readme" :> ProjectBranchReadmeEndpoint)
2223
:<|> ("releaseNotes" :> ProjectBranchReleaseNotesEndpoint)
24+
:<|> ("history" :> ProjectBranchHistoryEndpoint)
2325
:<|> ProjectBranchDetailsEndpoint
2426
:<|> ProjectBranchDeleteEndpoint
2527
:<|> CodeBrowseAPI
@@ -29,6 +31,11 @@ type ProjectBranchDetailsEndpoint = Get '[JSON] ShareBranch
2931

3032
type ProjectBranchDeleteEndpoint = Delete '[JSON] ()
3133

34+
type ProjectBranchHistoryEndpoint =
35+
QueryParam "cursor" (Cursor CausalHistoryCursor)
36+
:> QueryParam "limit" Limit
37+
:> Get '[JSON] BranchHistoryResponse
38+
3239
type ProjectBranchReadmeEndpoint =
3340
QueryParam "rootHash" CausalHash
3441
:> Get '[JSON] (Cached JSON ReadmeResponse)

share-api/src/Share/Web/Share/Branches/Impl.hs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Share.IDs (BranchId, BranchShortHand (..), ProjectBranchShortHand (..), P
2020
import Share.IDs qualified as IDs
2121
import Share.OAuth.Session
2222
import Share.Postgres qualified as PG
23+
import Share.Postgres.Causal.Queries (CausalHistoryCursor)
2324
import Share.Postgres.Causal.Queries qualified as CausalQ
2425
import Share.Postgres.Contributions.Queries qualified as ContributionsQ
2526
import Share.Postgres.IDs (CausalId)
@@ -40,7 +41,7 @@ import Share.Web.Authorization qualified as AuthZ
4041
import Share.Web.Errors
4142
import Share.Web.Share.Branches.API (ListBranchesCursor)
4243
import Share.Web.Share.Branches.API qualified as API
43-
import Share.Web.Share.Branches.Types (BranchKindFilter (..), ShareBranch (..))
44+
import Share.Web.Share.Branches.Types (BranchHistoryCausal (..), BranchHistoryEntry (..), BranchHistoryResponse (..), BranchKindFilter (..), ShareBranch (..))
4445
import Share.Web.Share.Branches.Types qualified as API
4546
import Share.Web.Share.CodeBrowsing.API qualified as API
4647
import Share.Web.Share.Contributions.Types
@@ -87,6 +88,7 @@ branchesServer session userHandle projectSlug =
8788
hoistServer (Proxy @API.ProjectBranchResourceAPI) (addTags branchShortHand) $
8889
( getProjectBranchReadmeEndpoint session userHandle projectSlug branchShortHand
8990
:<|> getProjectBranchReleaseNotesEndpoint session userHandle projectSlug branchShortHand
91+
:<|> branchHistoryEndpoint session userHandle projectSlug branchShortHand
9092
:<|> getProjectBranchDetailsEndpoint session userHandle projectSlug branchShortHand
9193
:<|> deleteProjectBranchEndpoint session userHandle projectSlug branchShortHand
9294
:<|> branchCodeBrowsingServer session userHandle projectSlug branchShortHand
@@ -491,6 +493,37 @@ deleteProjectBranchEndpoint session userHandle projectSlug branchShortHand = do
491493
PG.runTransaction $ Q.softDeleteBranch branchId
492494
pure ()
493495

496+
branchHistoryEndpoint ::
497+
Maybe Session ->
498+
UserHandle ->
499+
ProjectSlug ->
500+
BranchShortHand ->
501+
Maybe (Cursor CausalHistoryCursor) ->
502+
Maybe Limit ->
503+
WebApp BranchHistoryResponse
504+
branchHistoryEndpoint (AuthN.MaybeAuthedUserID callerUserId) userHandle projectSlug branchRef@(BranchShortHand {contributorHandle, branchName}) mayCursor mayLimit = do
505+
(Project {ownerUserId = projectOwnerUserId, projectId}, Branch {causal = branchHead, contributorId}) <- getProjectBranch projectBranchShortHand
506+
authZReceipt <- AuthZ.permissionGuard $ AuthZ.checkProjectBranchRead callerUserId projectId
507+
let codebaseLoc = Codebase.codebaseLocationForProjectBranchCodebase projectOwnerUserId contributorId
508+
let codebase = Codebase.codebaseEnv authZReceipt codebaseLoc
509+
causalId <- resolveRootHash codebase branchHead Nothing
510+
PG.runTransaction do
511+
history <-
512+
CausalQ.pagedCausalAncestors causalId limit mayCursor
513+
<&> fmap \(causalHash) ->
514+
BranchHistoryCausalEntry (BranchHistoryCausal {causalHash})
515+
pure $
516+
BranchHistoryResponse
517+
{ projectRef,
518+
branchRef,
519+
history
520+
}
521+
where
522+
projectRef = ProjectShortHand {userHandle, projectSlug}
523+
limit = fromMaybe defaultLimit mayLimit
524+
defaultLimit = Limit 20
525+
projectBranchShortHand = ProjectBranchShortHand {userHandle, projectSlug, contributorHandle, branchName}
526+
494527
getProjectBranchDocEndpoint ::
495528
Text ->
496529
Set NameSegment ->

share-api/src/Share/Web/Share/Branches/Types.hs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import Servant.API (FromHttpApiData (..), ToHttpApiData (..))
1212
import Share.Branch (Branch (..))
1313
import Share.IDs
1414
import Share.IDs qualified as IDs
15+
import Share.Postgres.Causal.Queries (CausalHistoryCursor)
1516
import Share.Postgres.IDs
17+
import Share.Prelude
18+
import Share.Utils.API (Paged, (:++) (..))
1619
import Share.Web.Share.Contributions.Types (ShareContribution)
1720
import Share.Web.Share.DisplayInfo.Types (UserDisplayInfo)
1821
import Share.Web.Share.Projects.Types
@@ -75,3 +78,68 @@ instance ToHttpApiData BranchKindFilter where
7578
toQueryParam AllBranchKinds = "all"
7679
toQueryParam OnlyCoreBranches = "core"
7780
toQueryParam OnlyContributorBranches = "contributor"
81+
82+
-- {
83+
-- "projectRef": "@unison/base",
84+
-- "branchRef": "main",
85+
-- "prevCursor": "c-asdf-1234",
86+
-- "nextCursor": "c-asdf-1234",
87+
-- "history": [
88+
-- {
89+
-- "tag": "Changeset",
90+
-- "causalHash": "#asdf-1234",
91+
-- }
92+
-- ]
93+
-- }
94+
95+
data BranchHistoryResponse = BranchHistoryResponse
96+
{ projectRef :: ProjectShortHand,
97+
branchRef :: BranchShortHand,
98+
history :: Paged CausalHistoryCursor BranchHistoryEntry
99+
}
100+
deriving stock (Eq, Show)
101+
102+
instance ToJSON BranchHistoryResponse where
103+
toJSON BranchHistoryResponse {..} =
104+
object
105+
[ "projectRef" .= IDs.toText @ProjectShortHand projectRef,
106+
"branchRef" .= IDs.toText @BranchShortHand branchRef,
107+
"history" .= history
108+
]
109+
110+
instance FromJSON BranchHistoryResponse where
111+
parseJSON = withObject "BranchHistoryResponse" $ \o ->
112+
BranchHistoryResponse
113+
<$> (o .: "projectRef")
114+
<*> (o .: "branchRef")
115+
<*> o .: "history"
116+
117+
data BranchHistoryCausal = BranchHistoryCausal
118+
{ causalHash :: CausalHash
119+
}
120+
deriving stock (Eq, Show)
121+
122+
instance ToJSON BranchHistoryCausal where
123+
toJSON BranchHistoryCausal {..} =
124+
object
125+
[ "causalHash" .= causalHash
126+
]
127+
128+
instance FromJSON BranchHistoryCausal where
129+
parseJSON = withObject "BranchHistoryCausal" $ \o ->
130+
BranchHistoryCausal
131+
<$> o .: "causalHash"
132+
133+
data BranchHistoryEntry
134+
= BranchHistoryCausalEntry BranchHistoryCausal
135+
deriving stock (Eq, Show)
136+
137+
instance ToJSON BranchHistoryEntry where
138+
toJSON = \case
139+
(BranchHistoryCausalEntry causal) ->
140+
toJSON (causal :++ (object ["tag" .= ("Changeset" :: Text)]))
141+
142+
instance FromJSON BranchHistoryEntry where
143+
parseJSON v = do
144+
causal <- parseJSON v
145+
return $ BranchHistoryCausalEntry causal
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"body": {
3+
"branchRef": "main",
4+
"history": {
5+
"items": [
6+
{
7+
"causalHash": "qt7njnf877g2q9g1f44eaigmcrgsdphc74jkdoos0i1pq6hguo03697adec2cb8lvgp71d4st3ss59g0haut5pfs5rpd4dru6pidt0g",
8+
"tag": "Changeset"
9+
},
10+
{
11+
"causalHash": "2k8f975ovhkm64bvog66rr8i7e4dripu8nkl7u62oif96if23lh5fh73n8p9qg21or98n4aljunidn6avonqpt1eu971h74iqlmgk5g",
12+
"tag": "Changeset"
13+
},
14+
{
15+
"causalHash": "n8b2r8o7u4f8ct0u79rhamnb16l3jhhr8986nudm6cgvg2j1fetouu0ojuiums5vtt8imsnsa7ek6lt18tcq3pf9knsinpsiqrq1r5o",
16+
"tag": "Changeset"
17+
}
18+
],
19+
"nextCursor": "<CURSOR>",
20+
"prevCursor": "<CURSOR>"
21+
},
22+
"projectRef": "@transcripts/branch-with-history"
23+
},
24+
"status": [
25+
{
26+
"status_code": 200
27+
}
28+
]
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"body": {
3+
"branchRef": "main",
4+
"history": {
5+
"items": [
6+
{
7+
"causalHash": "bpm4dqkous3flfcu8t07jhirta2i5kondhbqa7u3plg2racaohhdupam5k1bm7pnlbhpuphih36mdufuhsnv2832ri45u1nvn0j4qr8",
8+
"tag": "Changeset"
9+
},
10+
{
11+
"causalHash": "6h6qn76m9053vmg8a36cilbdnolked4uqh6bgm4qkpflpmr2pji3pais6f74k7364avo0rqn9kgdfje8pqldph0u52u8kjc8j923v00",
12+
"tag": "Changeset"
13+
},
14+
{
15+
"causalHash": "hqul4i7u7gud3u5ovcdgbgdsmvfnh3o98baolc5f5g35aic1j84jtd97otnn0reuig39jnnsp7376j4adsko3v12o4h09vqc2drbbd8",
16+
"tag": "Changeset"
17+
}
18+
],
19+
"nextCursor": "<CURSOR>",
20+
"prevCursor": null
21+
},
22+
"projectRef": "@transcripts/branch-with-history"
23+
},
24+
"status": [
25+
{
26+
"status_code": 200
27+
}
28+
]
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"body": {
3+
"branchRef": "main",
4+
"history": {
5+
"items": [
6+
{
7+
"causalHash": "bpm4dqkous3flfcu8t07jhirta2i5kondhbqa7u3plg2racaohhdupam5k1bm7pnlbhpuphih36mdufuhsnv2832ri45u1nvn0j4qr8",
8+
"tag": "Changeset"
9+
},
10+
{
11+
"causalHash": "6h6qn76m9053vmg8a36cilbdnolked4uqh6bgm4qkpflpmr2pji3pais6f74k7364avo0rqn9kgdfje8pqldph0u52u8kjc8j923v00",
12+
"tag": "Changeset"
13+
},
14+
{
15+
"causalHash": "hqul4i7u7gud3u5ovcdgbgdsmvfnh3o98baolc5f5g35aic1j84jtd97otnn0reuig39jnnsp7376j4adsko3v12o4h09vqc2drbbd8",
16+
"tag": "Changeset"
17+
}
18+
],
19+
"nextCursor": "<CURSOR>",
20+
"prevCursor": null
21+
},
22+
"projectRef": "@transcripts/branch-with-history"
23+
},
24+
"status": [
25+
{
26+
"status_code": 200
27+
}
28+
]
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
```unison
2+
x1 = 1
3+
```
4+
5+
```ucm
6+
branch-with-history/main> update
7+
branch-with-history/main> alias.term x1 x2
8+
branch-with-history/main> alias.term x2 x3
9+
branch-with-history/main> alias.term x3 x4
10+
branch-with-history/main> alias.term x4 x5
11+
branch-with-history/main> alias.term x5 x6
12+
branch-with-history/main> alias.term x6 x7
13+
branch-with-history/main> alias.term x7 x8
14+
branch-with-history/main> alias.term x8 x9
15+
branch-with-history/main> alias.term x9 x10
16+
branch-with-history/main> history
17+
branch-with-history/main> push @transcripts/branch-with-history/main
18+
```
19+

transcripts/share-apis/branches/run.zsh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,15 @@ fetch "$test_user" DELETE branch-delete '/users/test/projects/publictestproject/
5555
# Branch should no longer exist
5656
fetch "$test_user" GET branch-details-deleted '/users/test/projects/publictestproject/branches/main'
5757

58+
59+
# Add some history to a branch.
60+
transcript_ucm transcript prelude.md
61+
62+
fetch "$transcripts_user" GET branch-history '/users/transcripts/projects/branch-with-history/branches/main/history?limit=3'
63+
64+
next_cursor=$(fetch_data_jq "$transcripts_user" GET branch-history-next-cursor '/users/transcripts/projects/branch-with-history/branches/main/history?limit=3' '.history.nextCursor')
65+
66+
fetch "$transcripts_user" GET branch-history-next-page "/users/transcripts/projects/branch-with-history/branches/main/history?limit=3&cursor=$next_cursor" '.history.prevCursor'
67+
prev_cursor=$(fetch_data_jq "$transcripts_user" GET branch-history-prev-cursor "/users/transcripts/projects/branch-with-history/branches/main/history?limit=3&cursor=$next_cursor" '.history.prevCursor')
68+
69+
fetch "$transcripts_user" GET branch-history-prev-page "/users/transcripts/projects/branch-with-history/branches/main/history?limit=3&cursor=$prev_cursor"

0 commit comments

Comments
 (0)