1module Main (main) where
3import Control.Exception (SomeException(..), displayException)
4import Data.Word (Word8, Word32, Word64)
5import Streamly.Test.Common (listEquals, checkListEqual, chooseInt)
6import Test.Hspec (Spec, hspec, describe)
7import Test.Hspec.QuickCheck
9 (arbitrary, forAll, elements, Property, property, listOf,
11import Test.QuickCheck.Monadic (monadicIO, assert, run)
13import Prelude hiding (sequence)
15import qualified Data.List as List
16import qualified Prelude
17import qualified Streamly.Internal.Data.Array.Foreign as A
18import qualified Streamly.Internal.Data.Fold as FL
19import qualified Streamly.Internal.Data.Parser as P
20import qualified Streamly.Internal.Data.Stream.IsStream as S
21import qualified Test.Hspec as H
23#if MIN_VERSION_QuickCheck(2,14,0)
25import Test.QuickCheck (chooseAny)
29import System.Random (Random(random))
30import Test.QuickCheck.Gen (Gen(MkGen))
32-- | Generates a random element over the natural range of `a`.
33chooseAny :: Random a => Gen a
34chooseAny = MkGen (\r _ -> let (x,_) = random r in x)
58 forAll (listOf $ chooseInt (min_value, max_value)) $ \ls ->
59 case (==) <$> S.parse (P.fromFold FL.sum) (S.fromList ls)
60 <*> S.fold FL.sum (S.fromList ls) of
61 Right is_equal -> is_equal
66 forAll (chooseInt (min_value, max_value)) $ \x ->
67 case S.parse (P.fromPure x) (S.fromList [1 :: Int]) of
73 forAll (chooseInt (min_value, max_value)) $ \x ->
74 case S.parse (P.fromEffect $ return x) (S.fromList [1 :: Int]) of
81 case S.parse (P.die "die test") (S.fromList [0 :: Int]) of
88 case S.parse (P.dieM (Right "die test")) (S.fromList [0 :: Int]) of
95 case S.parse (fail err) (S.fromList [0 :: Int]) of
97 Left (SomeException e) -> err == displayException e
99 err = "Testing MonadFail.fail."
101-- Element Parser Tests
105 forAll (chooseInt (1, max_length)) $ \list_length ->
106 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
107 case S.parse P.peek (S.fromList ls) of
108 Right head_value -> case ls of
109 head_ls : _ -> head_value == head_ls
115 property (case S.parse P.peek (S.fromList []) of
121 property (case S.parse P.eof (S.fromList []) of
127 forAll (chooseInt (1, max_length)) $ \list_length ->
128 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
129 case S.parse P.eof (S.fromList ls) of
133satisfyPass :: Property
135 forAll (chooseInt (mid_value, max_value)) $ \first_element ->
136 forAll (listOf (chooseInt (min_value, max_value))) $ \ls_tail ->
138 ls = first_element : ls_tail
139 predicate = (>= mid_value)
141 case S.parse (P.satisfy predicate) (S.fromList ls) of
142 Right r -> r == first_element
147 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
148 case S.parse (P.satisfy predicate) (S.fromList ls) of
149 Right r -> case ls of
151 (x : _) -> predicate x && (r == x)
154 (x : _) -> not $ predicate x
156 predicate = (>= mid_value)
160 forAll (chooseInt (1, max_value)) $ \int ->
161 property (case S.parse P.next (S.fromList [int]) of
162 Right (Just i) -> i == int
167 property (case S.parse P.next (S.fromList []) of
168 Right Nothing -> True
171-- Sequence Parsers Tests
172takeBetweenPass :: Property
174 forAll (chooseInt (min_value, max_value)) $ \m ->
175 forAll (chooseInt (m, max_value)) $ \n ->
176 forAll (chooseInt (m, max_value)) $ \list_length ->
177 forAll (vectorOf list_length (chooseInt (min_value, max_value)))
179 case S.parse (P.takeBetween m n FL.toList)
182 let lpl = Prelude.length parsed_list
183 in checkListEqual parsed_list
184 $ Prelude.take lpl ls
185 Left _ -> property False
187takeBetween :: Property
189 forAll (chooseInt (min_value, max_value)) $ \m ->
190 forAll (chooseInt (min_value, max_value)) $ \n ->
191 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
197 let inputLen = Prelude.length ls
198 in case S.parse (P.takeBetween m n FL.toList) (S.fromList ls) of
200 let parsedLen = Prelude.length xs
201 in if inputLen >= m && parsedLen >= m && parsedLen <= n
202 then checkListEqual xs $ Prelude.take parsedLen ls
205 property ((m >= 0 && n >= 0 && m > n) || inputLen < m)
207takeEQPass :: Property
209 forAll (chooseInt (min_value, max_value)) $ \n ->
210 forAll (chooseInt (n, max_value)) $ \list_length ->
211 forAll (vectorOf list_length
212 (chooseInt (min_value, max_value))) $ \ls ->
213 case S.parse (P.takeEQ n FL.toList) (S.fromList ls) of
215 checkListEqual parsed_list (Prelude.take n ls)
216 Left _ -> property False
220 forAll (chooseInt (min_value, max_value)) $ \n ->
221 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
223 list_length = Prelude.length ls
225 case S.parse (P.takeEQ n FL.toList) (S.fromList ls) of
228 then checkListEqual parsed_list (Prelude.take n ls)
230 Left _ -> property (n > list_length)
232takeGEPass :: Property
234 forAll (chooseInt (min_value, max_value)) $ \n ->
235 forAll (chooseInt (n, max_value)) $ \list_length ->
236 forAll (vectorOf list_length (chooseInt (min_value, max_value)))
238 case S.parse (P.takeGE n FL.toList) (S.fromList ls) of
239 Right parsed_list -> checkListEqual parsed_list ls
240 Left _ -> property False
244 forAll (chooseInt (min_value, max_value)) $ \n ->
245 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
247 list_length = Prelude.length ls
249 case S.parse (P.takeGE n FL.toList) (S.fromList ls) of
252 then checkListEqual parsed_list ls
254 Left _ -> property (n > list_length)
258 -> FL.Fold (Either SomeException) Int [Int]
259 -> P.Parser (Either SomeException) Int [Int]
261 -> (Int -> [Int] -> [Int])
263nLessThanEqual0 tk ltk =
264 forAll (elements [0, (-1)]) $ \n ->
265 forAll (listOf arbitrary) $ \ls ->
266 case S.parse (tk n FL.toList) (S.fromList ls) of
267 Right parsed_list -> checkListEqual parsed_list (ltk n ls)
268 Left _ -> property False
270takeProperties :: Spec
272 describe "take combinators when n <= 0/" $ do
273 prop "takeEQ n FL.toList = []" $
274 nLessThanEqual0 P.takeEQ (\_ -> const [])
275 prop "takeGE n FL.toList xs = xs" $
276 nLessThanEqual0 P.takeGE (\_ -> id)
278-- lookAheadPass :: Property
280-- forAll (chooseInt (min_value + 1, max_value)) $ \n ->
282-- takeWithoutConsume = P.lookAhead $ P.take n FL.toList
284-- parsed_list_1 <- takeWithoutConsume
285-- parsed_list_2 <- takeWithoutConsume
286-- return (parsed_list_1, parsed_list_2)
288-- forAll (chooseInt (n, max_value)) $ \list_length ->
289-- forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
290-- case S.parse parseTwice (S.fromList ls) of
291-- Right (ls_1, ls_2) -> checkListEqual ls_1 ls_2 .&&. checkListEqual ls_1 (Prelude.take n ls)
292-- Left _ -> property $ False
294-- lookAheadFail :: Property
296-- forAll (chooseInt (min_value + 1, max_value)) $ \n ->
298-- takeWithoutConsume = P.lookAhead $ P.take n FL.toList
300-- parsed_list_1 <- takeWithoutConsume
301-- parsed_list_2 <- takeWithoutConsume
302-- return (parsed_list_1, parsed_list_2)
304-- forAll (chooseInt (min_value, n - 1)) $ \list_length ->
305-- forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
306-- case S.parse parseTwice (S.fromList ls) of
310-- lookAhead :: Property
312-- forAll (chooseInt (min_value, max_value)) $ \n ->
314-- takeWithoutConsume = P.lookAhead $ P.take n FL.toList
316-- parsed_list_1 <- takeWithoutConsume
317-- parsed_list_2 <- takeWithoutConsume
318-- return (parsed_list_1, parsed_list_2)
320-- forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
321-- case S.parse parseTwice (S.fromList ls) of
322-- Right (ls_1, ls_2) -> checkListEqual ls_1 ls_2 .&&. checkListEqual ls_1 (Prelude.take n ls)
323-- Left _ -> property ((list_length < n) || (list_length == n && n == 0))
325-- list_length = Prelude.length ls
327sliceSepByP :: Property
329 forAll (listOf (chooseInt (min_value, max_value ))) $ \ls ->
330 case S.parse (P.sliceSepByP predicate prsr) (S.fromList ls) of
332 checkListEqual parsed_list (tkwhl ls)
333 Left _ -> property False
336 prsr = P.many (P.satisfy (const True)) FL.toList
337 tkwhl ls = Prelude.takeWhile (not . predicate) ls
339sliceBeginWith :: Property
341 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
344 case S.parse parser (S.fromList ls1) of
346 if not $ Prelude.null ls1
348 let tls = Prelude.takeWhile (not . predicate) (tail ls1)
349 in checkListEqual parsed_list $
350 if predicate (head ls1)
352 else Prelude.takeWhile (not . predicate) ls1
353 else property $ Prelude.null parsed_list
354 Left _ -> property False
357 parser = P.sliceBeginWith predicate FL.toList
361 forAll (listOf (chooseInt (0, 1))) $ \ ls ->
362 case S.parse (P.takeWhile predicate FL.toList) (S.fromList ls) of
364 checkListEqual parsed_list (Prelude.takeWhile predicate ls)
365 Left _ -> property False
372 ((,) <$> chooseInt (min_value, max_value)
373 <*> listOf (chooseInt (0, 1)))
376 (P.takeP takeNum (P.fromFold FL.toList))
379 checkListEqual parsed_list (Prelude.take takeNum ls)
380 Left _ -> property False
382takeWhile1 :: Property
384 forAll (listOf (chooseInt (0, 1))) $ \ ls ->
385 case S.parse (P.takeWhile1 predicate FL.toList) (S.fromList ls) of
386 Right parsed_list -> case ls of
391 checkListEqual parsed_list
392 $ Prelude.takeWhile predicate ls
397 (x : _) -> property (not $ predicate x)
404 ((,,) <$> chooseInt (min_value, max_value)
405 <*> chooseInt (min_value, max_value)
406 <*> listOf (chooseInt (0, 1)))
408 case S.parse (P.choice [parser i, parser j]) (S.fromList ls) of
410 checkListEqual parsed_list $ take (min i j) ls
411 Left _ -> property False
415 parser i = P.fromFold (FL.take i FL.toList)
419 forAll (listOf (chooseInt (0, 1)))
421 case S.parse parser (S.fromList ls) of
422 Right parsed -> checkListEqual parsed (groupByLF ls)
423 Left _ -> property False
428 parser = P.groupBy cmp FL.toList
431 | otherwise = head $ List.groupBy cmp lst
435 forAll (listOf (elements [' ', 's']))
437 case S.parse parser (S.fromList ls) of
438 Right parsed -> checkListEqual parsed (words' ls)
439 Left _ -> property False
444 parser = P.many (P.wordBy predicate FL.toList) FL.toList
447 in if wrds == [] && length lst > 0 then [""] else wrds
449-- splitWithPass :: Property
451-- forAll (listOf (chooseInt (0, 1))) $ \ls ->
452-- case S.parse (P.serialWith (,) (P.satisfy (== 0)) (P.satisfy (== 1))) (S.fromList ls) of
453-- Right (result_first, result_second) -> case ls of
454-- 0 : 1 : _ -> (result_first == 0) && (result_second == 1)
456-- Left _ -> case ls of
460-- splitWithFailLeft :: Property
461-- splitWithFailLeft =
462-- property (case S.parse (P.serialWith (,) (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
466-- splitWithFailRight :: Property
467-- splitWithFailRight =
468-- property (case S.parse (P.serialWith (,) (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
472-- splitWithFailBoth :: Property
473-- splitWithFailBoth =
474-- property (case S.parse (P.serialWith (,) (P.die "die") (P.die "die")) (S.fromList [1 :: Int]) of
478-- teeWithPass :: Property
480-- forAll (chooseInt (0, 10000)) $ \n ->
481-- forAll (listOf (chooseInt (0, 1))) $ \ls ->
483-- prsr = P.take n FL.toList
485-- case S.parse (P.teeWith (,) prsr prsr) (S.fromList ls) of
486-- Right (ls_1, ls_2) -> checkListEqual (Prelude.take n ls) ls_1 .&&. checkListEqual ls_1 ls_2
487-- Left _ -> property False
489-- teeWithFailLeft :: Property
491-- property (case S.parse (P.teeWith (,) (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
495-- teeWithFailRight :: Property
497-- property (case S.parse (P.teeWith (,) (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
501-- teeWithFailBoth :: Property
503-- property (case S.parse (P.teeWith (,) (P.die "die") (P.die "die")) (S.fromList [1 :: Int]) of
507-- deintercalate :: Property
509-- forAll (listOf (chooseInt (0, 1))) $ \ls ->
510-- case S.parse (P.deintercalate concatFold prsr_1 concatFold prsr_2) (S.fromList ls) of
511-- Right parsed_list_tuple -> parsed_list_tuple == (partition (== 0) ls)
515-- prsr_1 = (P.takeWhile (== 0) FL.toList)
516-- prsr_2 = (P.takeWhile (== 1) FL.toList)
517-- concatFold = FL.Fold (\concatList curr_list -> return $ concatList ++ curr_list) (return []) return
519-- shortestPass :: Property
521-- forAll (listOf (chooseInt(min_value, max_value))) $ \ls ->
523-- prsr_1 = P.takeWhile (<= (mid_value `Prelude.div` 2)) FL.toList
524-- prsr_2 = P.takeWhile (<= mid_value) FL.toList
525-- prsr_shortest = P.shortest prsr_1 prsr_2
527-- case S.parse prsr_shortest (S.fromList ls) of
528-- Right short_list -> checkListEqual short_list (Prelude.takeWhile (<= 2500) ls)
529-- Left _ -> property False
531-- shortestFailLeft :: Property
533-- property (case S.parse (P.shortest (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
537-- shortestFailRight :: Property
538-- shortestFailRight =
539-- property (case S.parse (P.shortest (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
543-- shortestFailBoth :: Property
545-- property (case S.parse (P.shortest (P.die "die") (P.die "die")) (S.fromList [1 :: Int]) of
551 forAll (listOf (chooseInt (0, 1))) $ \ls ->
552 let fldstp conL currL = return $ FL.Partial $ conL ++ currL
553 concatFold = FL.Fold fldstp (return (FL.Partial [])) return
555 flip P.many concatFold $ P.fromFold $ FL.takeEndBy_ (== 1) FL.toList
557 case S.parse prsr (S.fromList ls) of
558 Right res_list -> checkListEqual res_list
559 $ Prelude.filter (== 0) ls
560 Left _ -> property False
562-- many_empty :: Property
564-- property (case S.parse (P.many FL.toList (P.die "die")) (S.fromList [1 :: Int]) of
565-- Right res_list -> checkListEqual res_list ([] :: [Int])
566-- Left _ -> property False)
570 forAll (listOf (chooseInt (0, 1))) $ \genLs ->
573 fldstp conL currL = return $ FL.Partial $ conL ++ currL
574 concatFold = FL.Fold fldstp (return (FL.Partial [])) return
576 flip P.some concatFold $ P.fromFold $ FL.takeEndBy_ (== 1) FL.toList
578 case S.parse prsr (S.fromList ls) of
579 Right res_list -> res_list == Prelude.filter (== 0) ls
582-- someFail :: Property
584-- property (case S.parse (P.some FL.toList (P.die "die")) (S.fromList [1 :: Int]) of
588-------------------------------------------------------------------------------
590-------------------------------------------------------------------------------
592applicative :: Property
594 forAll (listOf (chooseAny :: Gen Int)) $ \ list1 ->
595 forAll (listOf (chooseAny :: Gen Int)) $ \ list2 ->
598 <$> P.fromFold (FL.take (length list1) FL.toList)
599 <*> P.fromFold (FL.take (length list2) FL.toList)
602 run $ S.parse parser (S.fromList $ list1 ++ list2)
603 listEquals (==) olist1 list1
604 listEquals (==) olist2 list2
608 forAll (vectorOf 11 (listOf (chooseAny :: Gen Int))) $ \ ins ->
609 let p xs = P.fromFold (FL.take (length xs) FL.toList)
613 (Prelude.sequence $ fmap p ins)
614 (S.fromList $ concat ins)
615 listEquals (==) outs ins
619 forAll (listOf (chooseAny :: Gen Int)) $ \ list1 ->
620 forAll (listOf (chooseAny :: Gen Int)) $ \ list2 ->
622 olist1 <- P.fromFold (FL.take (length list1) FL.toList)
623 olist2 <- P.fromFold (FL.take (length list2) FL.toList)
624 return (olist1, olist2)
627 run $ S.parse parser (S.fromList $ list1 ++ list2)
628 listEquals (==) olist1 list1
629 listEquals (==) olist2 list2
631-------------------------------------------------------------------------------
633-------------------------------------------------------------------------------
637 forAll (chooseInt (1,100)) $ \len ->
638 forAll (listOf (vectorOf len (chooseAny :: Gen Int))) $ \ ins ->
641 let p = P.fromFold $ FL.take len FL.toList
644 $ S.parseMany p (S.fromList $ concat ins)
645 listEquals (==) outs ins
647-------------------------------------------------------------------------------
648-- Test for a particular case hit during fs events testing
649-------------------------------------------------------------------------------
652evId = [96,238,17,9,0,0,0,0]
655evFlags = [0,4,1,0,0,0,0,0]
658evPathLen = [71,0,0,0,0,0,0,0]
662 [47,85,115,101,114,115,47,118,111,108,47,118,101,109,98,97,47,99,111,109
663 ,112,111,115,101,119,101 ,108,108,45,116,101,99,104,47,69,110,103,47,112
664 ,114,111,106,101,99,116,115,47,115,116,114,101,97,109,108,121,47,115,116
665 ,114,101,97,109,108,121,47,116,109,112,47,122,122
669event = evId ++ evFlags ++ evPathLen ++ evPath
673 , eventFlags :: Word32
674 , eventAbsPath :: A.Array Word8
675 } deriving (Show, Ord, Eq)
677readOneEvent :: P.Parser IO Word8 Event
679 arr <- P.takeEQ 24 (A.writeN 24)
680 let arr1 = A.castUnsafe arr :: A.Array Word64
681 eid = A.unsafeIndex 0 arr1
682 eflags = A.unsafeIndex 1 arr1
683 pathLen = fromIntegral $ A.unsafeIndex 2 arr1
684 -- XXX handle if pathLen is 0
685 path <- P.takeEQ pathLen (A.writeN pathLen)
688 , eventFlags = fromIntegral eflags
689 , eventAbsPath = path
692parseMany2Events :: Property
698 $ S.parseMany readOneEvent
699 $ S.fromList (concat (replicate 2 event))
701 assert (length xs == 2)
702 -- XXX assuming little endian machine
704 { eventId = 152170080
706 , eventAbsPath = A.fromList evPath
708 in listEquals (==) xs (replicate 2 ev)
710manyEqParseMany :: Property
712 forAll (listOf (chooseInt (0, 100))) $ \lst ->
713 forAll (chooseInt (1, 100)) $ \i ->
715 let strm = S.fromList lst
716 r1 <- run $ S.parse (P.many (split i) FL.toList) strm
717 r2 <- run $ S.toList $ S.parseMany (split i) strm
722 split i = P.fromFold (FL.take i FL.toList)
724-------------------------------------------------------------------------------
726-------------------------------------------------------------------------------
729moduleName = "Data.Parser"
735 modifyMaxSuccess (const maxTestCount) $ do
736 describe moduleName $ do
738 describe "Instances" $ do
739 prop "applicative" applicative
741 prop "sequence" sequence
743 describe "Stream parsing" $ do
744 prop "parseMany" parseMany
745 prop "parseMany2Events" parseMany2Events
747 describe "test for accumulator" $ do
748 prop "P.fromFold FL.sum = FL.sum" fromFold
749 prop "fromPure value provided" fromPure
750 prop "fromPure monadic value provided" fromEffect
751 prop "fail err = Left (SomeException (ParseError err))" parserFail
752 prop "always fail" die
753 prop "always fail but monadic" dieM
755 describe "test for element parser" $ do
756 prop "peek = head with list length > 0" peekPass
757 prop "peek fail on []" peekFail
758 prop "eof pass on []" eofPass
759 prop "eof fail on non-empty list" eofFail
760 prop "first element exists and >= mid_value" satisfyPass
761 prop "next pass on [Int]" nextPass
762 prop "next fail on []" next
763 prop "check first element exists and satisfies predicate" satisfy
765 describe "test for sequence parser" $ do
766 prop "P.takeBetween = Prelude.take when len >= m and len <= n"
768 prop ("P.takeBetween = Prelude.take when len >= m and len <= n and fail"
769 ++ "otherwise fail") Main.takeBetween
771 prop "P.takeEQ = Prelude.take when len >= n" takeEQPass
772 prop "P.takeEQ = Prelude.take when len >= n and fail otherwise"
774 prop "P.takeGE n ls = ls when len >= n" takeGEPass
775 prop "P.takeGE n ls = ls when len >= n and fail otherwise" Main.takeGE
776 -- prop "lookAhead . take n >> lookAhead . take n = lookAhead . take n" lookAheadPass
777 -- prop "Fail when stream length exceeded" lookAheadFail
778 -- prop "lookAhead . take n >> lookAhead . take n = lookAhead . take n, else fail" lookAhead
779 prop "P.sliceSepByP test" Main.sliceSepByP
780 prop ("P.sliceBeginWith pred = head : Prelude.takeWhile (not . pred)"
781 ++ " tail") sliceBeginWith
782 prop "P.takeWhile = Prelude.takeWhile" Main.takeWhile
783 prop ("P.takeWhile1 = Prelude.takeWhile if taken something,"
784 ++ " else check why failed") takeWhile1
785 prop ("P.takeP = Prelude.take") takeP
786 prop "P.groupBy = Prelude.head . Prelude.groupBy" groupBy
787 prop "many (P.wordBy ' ') = words'" wordBy
789 -- prop "" splitWithPass
790 -- prop "" splitWithFailLeft
791 -- prop "" splitWithFailRight
792 -- prop "" splitWithFailBoth
793 -- prop "" teeWithPass
794 -- prop "" teeWithFailLeft
795 -- prop "" teeWithFailRight
796 -- prop "" teeWithFailBoth
797 -- prop "" deintercalate
798 -- prop "" shortestPass
799 -- prop "" shortestFailLeft
800 -- prop "" shortestFailRight
801 -- prop "" shortestFailBoth
802 prop ("P.many concatFold $ P.takeEndBy_ (== 1) FL.toList ="
803 ++ "Prelude.filter (== 0)") many
804 -- prop "[] due to parser being die" many_empty
805 prop ("P.some concatFold $ P.takeEndBy_ (== 1) FL.toList ="
806 ++ "Prelude.filter (== 0)") some
807 -- prop "fail due to parser being die" someFail
808 prop "P.many == S.parseMany" manyEqParseMany
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list