Print this page
teamware must die

Split Close
Expand all
Collapse all
          --- old/usr/src/tools/scripts/webrev.sh
          +++ new/usr/src/tools/scripts/webrev.sh
↓ open down ↓ 638 lines elided ↑ open up ↑
 639  639  #  The two HTML files are then combined into a single piece of HTML that
 640  640  #  uses an HTML table construct to present the files side by side.  You'll
 641  641  #  notice that the changes are color-coded:
 642  642  #
 643  643  #   black     - unchanged lines
 644  644  #   blue      - changed lines
 645  645  #   bold blue - new lines
 646  646  #   brown     - deleted lines
 647  647  #
 648  648  #  Blank lines are inserted in each file to keep unchanged lines in sync
 649      -#  (side-by-side).  This format is familiar to users of sdiff(1) or
 650      -#  Teamware's filemerge tool.
      649 +#  (side-by-side).  This format is familiar to users of sdiff(1).
 651  650  #
 652  651  sdiff_to_html()
 653  652  {
 654  653          diff -b $1 $2 > /tmp/$$.diffs
 655  654  
 656  655          TNAME=$3
 657  656          TPATH=$4
 658  657          COMMENT=$5
 659  658  
 660  659          #
↓ open down ↓ 764 lines elided ↑ open up ↑
1425 1424  
1426 1425          print "$HTML<head>$STDHEAD"
1427 1426          print "<title>$WNAME $WHICH $TNAME</title>"
1428 1427          print "<body id=\"SUNWwebrev\">"
1429 1428          print "<pre>"
1430 1429          html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1431 1430          print "</pre></body></html>"
1432 1431  }
1433 1432  
1434 1433  #
1435      -# comments_from_teamware {text|html} parent-file child-file
1436      -#
1437      -# Find the first delta in the child that's not in the parent.  Get the
1438      -# newest delta from the parent, get all deltas from the child starting
1439      -# with that delta, and then get all info starting with the second oldest
1440      -# delta in that list (the first delta unique to the child).
1441      -#
1442      -# This code adapted from Bill Shannon's "spc" script
1443      -#
1444      -comments_from_teamware()
1445      -{
1446      -        fmt=$1
1447      -        pfile=$PWS/$2
1448      -        cfile=$CWS/$3
1449      -
1450      -        if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then
1451      -                pfile=$RWS/$2
1452      -        fi
1453      -
1454      -        if [[ -f $pfile ]]; then
1455      -                psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
1456      -        else
1457      -                psid=1.1
1458      -        fi
1459      -
1460      -        set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
1461      -        N=${#sids[@]}
1462      -
1463      -        nawkprg='
1464      -                /^COMMENTS:/    {p=1; continue}
1465      -                /^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1466      -                NF == 0u        { continue }
1467      -                {if (p==0) continue; print $0 }'
1468      -
1469      -        if [[ $N -ge 2 ]]; then
1470      -                sid1=${sids[$((N-2))]}  # Gets 2nd to last sid
1471      -
1472      -                if [[ $fmt == "text" ]]; then
1473      -                        $SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1474      -                            $AWK "$nawkprg"
1475      -                        return
1476      -                fi
1477      -
1478      -                $SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1479      -                    html_quote | its2url | $AWK "$nawkprg"
1480      -        fi
1481      -}
1482      -
1483      -#
1484 1434  # comments_from_wx {text|html} filepath
1485 1435  #
1486 1436  # Given the pathname of a file, find its location in a "wx" active
1487 1437  # file list and print the following comment.  Output is either text or
1488 1438  # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1489 1439  # are turned into URLs.
1490 1440  #
1491 1441  # This is also used with Mercurial and the file list provided by hg-active.
1492 1442  #
1493 1443  comments_from_wx()
↓ open down ↓ 35 lines elided ↑ open up ↑
1529 1479  
1530 1480          if [[ -n $Nflag ]]; then
1531 1481                  return
1532 1482          fi
1533 1483          #
1534 1484          # Mercurial support uses a file list in wx format, so this
1535 1485          # will be used there, too
1536 1486          #
1537 1487          if [[ -n $wxfile ]]; then
1538 1488                  comments_from_wx $fmt $p
1539      -        else
1540      -                if [[ $SCM_MODE == "teamware" ]]; then
1541      -                        comments_from_teamware $fmt $pp $p
1542      -                fi
1543 1489          fi
1544 1490  }
1545 1491  
1546 1492  #
1547 1493  # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1548 1494  #
1549 1495  # Print out Code Inspection figures similar to sccs-prt(1) format.
1550 1496  #
1551 1497  function printCI
1552 1498  {
↓ open down ↓ 158 lines elided ↑ open up ↑
1711 1657            while (getline) {
1712 1658                  if (NF == 0) { c = -c; continue }
1713 1659                  if (c > 0) print
1714 1660            }
1715 1661          }' $wxfile > $FLIST
1716 1662  
1717 1663          print " Done."
1718 1664  }
1719 1665  
1720 1666  #
1721      -# flist_from_teamware [ <args-to-putback-n> ]
1722      -#
1723      -# Generate the file list by extracting file names from a putback -n.  Some
1724      -# names may come from the "update/create" messages and others from the
1725      -# "currently checked out" warning.  Renames are detected here too.  Extract
1726      -# values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1727      -# -n as well, but remove them if they are already defined.
1728      -#
1729      -function flist_from_teamware
1730      -{
1731      -        if [[ -n $codemgr_parent && -z $parent_webrev ]]; then
1732      -                if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1733      -                        print -u2 "parent $codemgr_parent doesn't look like a" \
1734      -                            "valid teamware workspace"
1735      -                        exit 1
1736      -                fi
1737      -                parent_args="-p $codemgr_parent"
1738      -        fi
1739      -
1740      -        print " File list from: 'putback -n $parent_args $*' ... \c"
1741      -
1742      -        putback -n $parent_args $* 2>&1 |
1743      -            $AWK '
1744      -                /^update:|^create:/     {print $2}
1745      -                /^Parent workspace:/    {printf("CODEMGR_PARENT=%s\n",$3)}
1746      -                /^Child workspace:/     {printf("CODEMGR_WS=%s\n",$3)}
1747      -                /^The following files are currently checked out/ {p = 1; continue}
1748      -                NF == 0                 {p=0 ; continue}
1749      -                /^rename/               {old=$3}
1750      -                $1 == "to:"             {print $2, old}
1751      -                /^"/                    {continue}
1752      -                p == 1                  {print $1}' |
1753      -            sort -r -k 1,1 -u | sort > $FLIST
1754      -
1755      -        print " Done."
1756      -}
1757      -
1758      -#
1759 1667  # Call hg-active to get the active list output in the wx active list format
1760 1668  #
1761 1669  function hg_active_wxfile
1762 1670  {
1763 1671          typeset child=$1
1764 1672          typeset parent=$2
1765 1673  
1766 1674          TMPFLIST=/tmp/$$.active
1767 1675          $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1768 1676          wxfile=$TMPFLIST
↓ open down ↓ 140 lines elided ↑ open up ↑
1909 1817  }
1910 1818  
1911 1819  function look_for_prog
1912 1820  {
1913 1821          typeset path
1914 1822          typeset ppath
1915 1823          typeset progname=$1
1916 1824  
1917 1825          ppath=$PATH
1918 1826          ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1919      -        ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1920      -        ppath=$ppath:/opt/onbld/bin/`uname -p`
     1827 +        ppath=$ppath:/opt/onbld/bin:/opt/onbld/bin/`uname -p`
1921 1828  
1922 1829          PATH=$ppath prog=`whence $progname`
1923 1830          if [[ -n $prog ]]; then
1924 1831                  print $prog
1925 1832          fi
1926 1833  }
1927 1834  
1928 1835  function get_file_mode
1929 1836  {
1930 1837          $PERL -e '
1931 1838                  if (@stat = stat($ARGV[0])) {
1932 1839                          $mode = $stat[2] & 0777;
1933 1840                          printf "%03o\n", $mode;
1934 1841                          exit 0;
1935 1842                  } else {
1936 1843                          exit 1;
1937 1844                  }
1938 1845              ' $1
1939 1846  }
1940 1847  
1941      -function build_old_new_teamware
1942      -{
1943      -        typeset olddir="$1"
1944      -        typeset newdir="$2"
1945      -
1946      -        # If the child's version doesn't exist then
1947      -        # get a readonly copy.
1948      -
1949      -        if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1950      -                $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1951      -        fi
1952      -
1953      -        # The following two sections propagate file permissions the
1954      -        # same way SCCS does.  If the file is already under version
1955      -        # control, always use permissions from the SCCS/s.file.  If
1956      -        # the file is not under SCCS control, use permissions from the
1957      -        # working copy.  In all cases, the file copied to the webrev
1958      -        # is set to read only, and group/other permissions are set to
1959      -        # match those of the file owner.  This way, even if the file
1960      -        # is currently checked out, the webrev will display the final
1961      -        # permissions that would result after check in.
1962      -
1963      -        #
1964      -        # Snag new version of file.
1965      -        #
1966      -        rm -f $newdir/$DIR/$F
1967      -        cp $CWS/$DIR/$F $newdir/$DIR/$F
1968      -        if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1969      -                chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1970      -                    $newdir/$DIR/$F
1971      -        fi
1972      -        chmod u-w,go=u $newdir/$DIR/$F
1973      -
1974      -        #
1975      -        # Get the parent's version of the file. First see whether the
1976      -        # child's version is checked out and get the parent's version
1977      -        # with keywords expanded or unexpanded as appropriate.
1978      -        #
1979      -        if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1980      -            ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1981      -                # Parent is not a real workspace, but just a raw
1982      -                # directory tree - use the file that's there as
1983      -                # the old file.
1984      -
1985      -                rm -f $olddir/$PDIR/$PF
1986      -                cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1987      -        else
1988      -                if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1989      -                        real_parent=$PWS
1990      -                else
1991      -                        real_parent=$RWS
1992      -                fi
1993      -
1994      -                rm -f $olddir/$PDIR/$PF
1995      -
1996      -                if [[ -f $real_parent/$PDIR/$PF ]]; then
1997      -                        if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1998      -                                $SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1999      -                                    $olddir/$PDIR/$PF
2000      -                        else
2001      -                                $SCCS get -s -p    $real_parent/$PDIR/$PF > \
2002      -                                    $olddir/$PDIR/$PF
2003      -                        fi
2004      -                        chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
2005      -                            $olddir/$PDIR/$PF
2006      -                fi
2007      -        fi
2008      -        if [[ -f $olddir/$PDIR/$PF ]]; then
2009      -                chmod u-w,go=u $olddir/$PDIR/$PF
2010      -        fi
2011      -}
2012      -
2013 1848  function build_old_new_mercurial
2014 1849  {
2015 1850          typeset olddir="$1"
2016 1851          typeset newdir="$2"
2017 1852          typeset old_mode=
2018 1853          typeset new_mode=
2019 1854          typeset file
2020 1855  
2021 1856          #
2022 1857          # Get old file mode, from the parent revision manifest entry.
↓ open down ↓ 170 lines elided ↑ open up ↑
2193 2028          typeset CWS=$5
2194 2029          typeset DIR=$6
2195 2030          typeset F=$7
2196 2031  
2197 2032          typeset olddir="$WDIR/raw_files/old"
2198 2033          typeset newdir="$WDIR/raw_files/new"
2199 2034  
2200 2035          mkdir -p $olddir/$PDIR
2201 2036          mkdir -p $newdir/$DIR
2202 2037  
2203      -        if [[ $SCM_MODE == "teamware" ]]; then
2204      -                build_old_new_teamware "$olddir" "$newdir"
2205      -        elif [[ $SCM_MODE == "mercurial" ]]; then
     2038 +        if [[ $SCM_MODE == "mercurial" ]]; then
2206 2039                  build_old_new_mercurial "$olddir" "$newdir"
2207 2040          elif [[ $SCM_MODE == "git" ]]; then
2208 2041                  build_old_new_git "$olddir" "$newdir"
2209 2042          elif [[ $SCM_MODE == "subversion" ]]; then
2210 2043                  build_old_new_subversion "$olddir" "$newdir"
2211 2044          elif [[ $SCM_MODE == "unknown" ]]; then
2212 2045                  build_old_new_unknown "$olddir" "$newdir"
2213 2046          fi
2214 2047  
2215 2048          if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
↓ open down ↓ 23 lines elided ↑ open up ↑
2239 2072          -o <outdir>: Output webrev to specified directory.
2240 2073          -p <compare-against>: Use specified parent wkspc or basis for comparison
2241 2074          -t <remote_target>: Specify remote destination for webrev upload
2242 2075          -U: upload the webrev to remote destination
2243 2076          -w <wxfile>: Use specified wx active file.
2244 2077  
2245 2078  Environment:
2246 2079          WDIR: Control the output directory.
2247 2080          WEBREV_TRASH_DIR: Set directory for webrev delete.
2248 2081  
2249      -SCM Specific Options:
2250      -        TeamWare: webrev [common-options] -l [arguments to 'putback']
2251      -
2252 2082  SCM Environment:
2253 2083          CODEMGR_WS: Workspace location.
2254 2084          CODEMGR_PARENT: Parent workspace location.
2255 2085  '
2256      -
2257 2086          exit 2
2258 2087  }
2259 2088  
2260 2089  #
2261 2090  #
2262 2091  # Main program starts here
2263 2092  #
2264 2093  #
2265 2094  
2266 2095  trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
↓ open down ↓ 141 lines elided ↑ open up ↑
2408 2237          exit 1
2409 2238  fi
2410 2239  
2411 2240  #
2412 2241  # For the invocation "webrev -n -U" with no other options, webrev will assume
2413 2242  # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2414 2243  # $(basename ${CWS}).  So we need to get CWS set before we skip any remaining
2415 2244  # logic.
2416 2245  #
2417 2246  $WHICH_SCM | read SCM_MODE junk || exit 1
2418      -if [[ $SCM_MODE == "teamware" ]]; then
2419      -        #
2420      -        # Teamware priorities:
2421      -        # 1. CODEMGR_WS from the environment
2422      -        # 2. workspace name
2423      -        #
2424      -        [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2425      -        if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2426      -                print -u2 "$codemgr_ws: no such workspace"
2427      -                exit 1
2428      -        fi
2429      -        [[ -z $codemgr_ws ]] && codemgr_ws=$(workspace name)
2430      -        codemgr_ws=$(cd $codemgr_ws;print $PWD)
2431      -        CODEMGR_WS=$codemgr_ws
2432      -        CWS=$codemgr_ws
2433      -elif [[ $SCM_MODE == "mercurial" ]]; then
     2247 +if [[ $SCM_MODE == "mercurial" ]]; then
2434 2248          #
2435 2249          # Mercurial priorities:
2436 2250          # 1. hg root from CODEMGR_WS environment variable
2437 2251          # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2438 2252          #    usr/closed when we run webrev
2439 2253          # 2. hg root from directory of invocation
2440 2254          #
2441 2255          if [[ ${PWD} =~ "usr/closed" ]]; then
2442 2256                  testparent=${CODEMGR_WS}/usr/closed
2443 2257                  # If we're in OpenSolaris mode, we enforce a minor policy:
↓ open down ↓ 105 lines elided ↑ open up ↑
2549 2363          else
2550 2364                  flist_mode="auto"
2551 2365          fi
2552 2366  fi
2553 2367  
2554 2368  #
2555 2369  # Before we go on to further consider -l and -w, work out which SCM we think
2556 2370  # is in use.
2557 2371  #
2558 2372  case "$SCM_MODE" in
2559      -teamware|mercurial|git|subversion)
     2373 +mercurial|git|subversion)
2560 2374          ;;
2561 2375  unknown)
2562 2376          if [[ $flist_mode == "auto" ]]; then
2563 2377                  print -u2 "Unable to determine SCM in use and file list not specified"
2564 2378                  print -u2 "See which_scm(1) for SCM detection information."
2565 2379                  exit 1
2566 2380          fi
2567 2381          ;;
2568 2382  *)
2569 2383          if [[ $flist_mode == "auto" ]]; then
2570 2384                  print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2571 2385                  exit 1
2572 2386          fi
2573 2387          ;;
2574 2388  esac
2575 2389  
2576 2390  print -u2 "   SCM detected: $SCM_MODE"
2577 2391  
2578      -if [[ -n $lflag ]]; then
2579      -        #
2580      -        # If the -l flag is given instead of the name of a file list,
2581      -        # then generate the file list by extracting file names from a
2582      -        # putback -n.
2583      -        #
2584      -        shift $(($OPTIND - 1))
2585      -        if [[ $SCM_MODE == "teamware" ]]; then
2586      -                flist_from_teamware "$*"
2587      -        else
2588      -                print -u2 -- "Error: -l option only applies to TeamWare"
2589      -                exit 1
2590      -        fi
2591      -        flist_done=1
2592      -        shift $#
2593      -elif [[ -n $wflag ]]; then
     2392 +if [[ -n $wflag ]]; then
2594 2393          #
2595 2394          # If the -w is given then assume the file list is in Bonwick's "wx"
2596 2395          # command format, i.e.  pathname lines alternating with SCCS comment
2597 2396          # lines with blank lines as separators.  Use the SCCS comments later
2598 2397          # in building the index.html file.
2599 2398          #
2600 2399          shift $(($OPTIND - 1))
2601 2400          wxfile=$1
2602 2401          if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2603 2402                  if [[ -r $CODEMGR_WS/wx/active ]]; then
↓ open down ↓ 23 lines elided ↑ open up ↑
2627 2426  
2628 2427  if [[ $# -gt 0 ]]; then
2629 2428          print -u2 "WARNING: unused arguments: $*"
2630 2429  fi
2631 2430  
2632 2431  #
2633 2432  # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2634 2433  # and CODEMGR_WS as needed.  Here, we set the parent workspace.
2635 2434  #
2636 2435  
2637      -if [[ $SCM_MODE == "teamware" ]]; then
2638      -
2639      -        #
2640      -        # Teamware priorities:
2641      -        #
2642      -        #      1) via -p command line option
2643      -        #      2) in the user environment
2644      -        #      3) in the flist
2645      -        #      4) automatically based on the workspace
2646      -        #
2647      -
2648      -        #
2649      -        # For 1, codemgr_parent will already be set.  Here's 2:
2650      -        #
2651      -        [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2652      -            codemgr_parent=$CODEMGR_PARENT
2653      -        if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2654      -                print -u2 "$codemgr_parent: no such directory"
2655      -                exit 1
2656      -        fi
2657      -
2658      -        #
2659      -        # If we're in auto-detect mode and we haven't already gotten the file
2660      -        # list, then see if we can get it by probing for wx.
2661      -        #
2662      -        if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2663      -                if [[ ! -x $WX ]]; then
2664      -                        print -u2 "WARNING: wx not found!"
2665      -                fi
2666      -
2667      -                #
2668      -                # We need to use wx list -w so that we get renamed files, etc.
2669      -                # but only if a wx active file exists-- otherwise wx will
2670      -                # hang asking us to initialize our wx information.
2671      -                #
2672      -                if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2673      -                        print -u2 " File list from: 'wx list -w' ... \c"
2674      -                        $WX list -w > $FLIST
2675      -                        $WX comments > /tmp/$$.wx_comments
2676      -                        wxfile=/tmp/$$.wx_comments
2677      -                        print -u2 "done"
2678      -                        flist_done=1
2679      -                fi
2680      -        fi
2681      -
2682      -        #
2683      -        # If by hook or by crook we've gotten a file list by now (perhaps
2684      -        # from the command line), eval it to extract environment variables from
2685      -        # it: This is method 3 for finding the parent.
2686      -        #
2687      -        if [[ -z $flist_done ]]; then
2688      -                flist_from_teamware
2689      -        fi
2690      -        env_from_flist
2691      -
2692      -        #
2693      -        # (4) If we still don't have a value for codemgr_parent, get it
2694      -        # from workspace.
2695      -        #
2696      -        [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2697      -        if [[ ! -d $codemgr_parent ]]; then
2698      -                print -u2 "$CODEMGR_PARENT: no such parent workspace"
2699      -                exit 1
2700      -        fi
2701      -
2702      -        PWS=$codemgr_parent
2703      -
2704      -        [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2705      -
2706      -elif [[ $SCM_MODE == "mercurial" ]]; then
     2436 +if [[ $SCM_MODE == "mercurial" ]]; then
2707 2437          #
2708 2438          # Parent can either be specified with -p
2709 2439          # Specified with CODEMGR_PARENT in the environment
2710 2440          # or taken from hg's default path.
2711 2441          #
2712 2442  
2713 2443          if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2714 2444                  codemgr_parent=$CODEMGR_PARENT
2715 2445          fi
2716 2446  
↓ open down ↓ 959 lines elided ↑ open up ↑
3676 3406  
3677 3407          print "<!-- Add comments to explain changes in $P here -->"
3678 3408  
3679 3409          # Add count of changes.
3680 3410  
3681 3411          if [[ -f $F.count ]]; then
3682 3412              cat $F.count
3683 3413              rm $F.count
3684 3414          fi
3685 3415  
3686      -        if [[ $SCM_MODE == "teamware" ||
3687      -            $SCM_MODE == "mercurial" ||
     3416 +        if [[ $SCM_MODE == "mercurial" ||
3688 3417              $SCM_MODE == "unknown" ]]; then
3689 3418  
3690 3419                  # Include warnings for important file mode situations:
3691 3420                  # 1) New executable files
3692 3421                  # 2) Permission changes of any kind
3693 3422                  # 3) Existing executable files
3694 3423  
3695 3424                  old_mode=
3696 3425                  if [[ -f $WDIR/raw_files/old/$PP ]]; then
3697 3426                          old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
↓ open down ↓ 55 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX