/usr/bin/eg is in easygit 0.99-2.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 6500 6501 6502 6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095 7096 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141 7142 7143 7144 7145 7146 7147 7148 7149 7150 7151 7152 7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220 7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 7287 7288 7289 7290 7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 | #!/usr/bin/perl -w
## Easy GIT (eg), a frontend for git designed for former cvs and svn users
## Version .99
## Copyright 2008, 2009 by Elijah Newren, and others
## Licensed under GNU GPL, version 2.
## To use eg, simply stick this file in your path. Then fire off an
## 'eg help' to get oriented. You may also be interested in
## http://www.gnome.org/~newren/eg/git-for-svn-users.html
## to get a comparison to svn in terms of capabilities and commands.
## Webpage for eg: http://www.gnome.org/~newren/eg
package main;
use warnings;
use Getopt::Long;
use Cwd qw(getcwd abs_path);
use List::Util qw(max);
use File::Temp qw/ tempfile /;
# configurables
my $debug=0;
# globals :-(
my $outfh;
my $version = ".99";
my $eg_exec = abs_path($0);
my $git_cmd = "git"; # Includes any global args, thus "git --exec-path=..."
my $use_pager = -1;
my %command; # command=>{section, short_description} mapping
my $section = {
'creation' =>
{ order => 1,
desc => 'Creating repositories',
},
'discovery' =>
{ order => 2,
desc => 'Obtaining information about changes, history, & state',
},
'modification' =>
{ order => 3,
desc => 'Making, undoing, or recording changes',
},
'projects' =>
{ order => 4,
desc => 'Managing branches',
},
'collaboration' =>
{ order => 5,
desc => 'Collaboration'
},
'timesavers' =>
{ order => 6,
desc => 'Time saving commands'
},
'compatibility' =>
{ order => 7,
extra => 1,
desc => 'Commands provided solely for compatibility with other ' .
'prominent SCMs'
},
'misc' =>
{ order => 8,
extra => 1,
desc => 'Miscellaneous'
},
};
my ($curdir, $topdir, $gitdir);
## Commands to list in help even though we haven't overridden the git versions
## (yet, in most cases)
INIT {
%command = (
blame => {
unmodified_help => 1,
unmodified_behavior => 1,
extra => 1,
section => 'discovery',
about => 'Show what version and author last modified each line of a file'
},
bisect => {
unmodified_help => 1,
unmodified_behavior => 1,
section => 'timesavers',
about => 'Find the change that introduced a bug by binary search'
},
grep => {
unmodified_help => 1,
unmodified_behavior => 1,
extra => 1,
section => 'discovery',
about => 'Print lines of known files matching a pattern'
},
mv => {
unmodified_help => 1,
unmodified_behavior => 1,
section => 'modification',
about => 'Move or rename files (or directories or symlinks)'
},
);
}
#*************************************************************************#
#*************************************************************************#
#*************************************************************************#
# CLASSES DEFINING ACTIONS TO PERFORM #
#*************************************************************************#
#*************************************************************************#
#*************************************************************************#
###########################################################################
# subcommand, a base class for all eg subcommands #
###########################################################################
package subcommand;
sub new {
my $class = shift;
my $self = {git_repo_needed => 0, @_}; # Hashref initialized as we're told
bless($self, $class);
# Our "see also" section in help usually references the same subsection
# as our class name.
$self->{git_equivalent} = ref($self) if !defined $self->{git_equivalent};
# We allow direct instantiation of the subcommand class only if they
# provide a command name for us to pass to git.
if (ref($class) eq "subcommand" && !defined $self->{command}) {
die "Invalid subcommand usage"
}
# Most commands must be run inside a git working directory
unless (!$self->{git_repo_needed} || (@ARGV > 0 && $ARGV[0] eq "--help")) {
$self->{git_dir} = RepoUtil::git_dir();
die "Must be run inside a git repository!\n" if !defined $self->{git_dir};
}
# Many commands do not work if no commit has yet been made
if ($self->{initial_commit_error_msg} &&
RepoUtil::initial_commit() &&
(@ARGV < 1 || $ARGV[0] ne "--help")) {
die "$self->{initial_commit_error_msg}\n";
}
return $self;
}
sub help {
my $self = shift;
my $package_name = ref($self);
$package_name =~ s/_/-/; # Packages use underscores, commands use dashes
my $git_equiv = $self->{git_equivalent};
$git_equiv =~ s/_/-/; # Packages use underscores, commands use dashes
if ($package_name eq "subcommand") {
exit ExecUtil::execute("$git_cmd $self->{command} --help")
}
$ENV{"LESS"} = "FRSX" unless defined $ENV{"LESS"};
my $less = ($use_pager == 1) ? "less" :
($use_pager == 0) ? "cat" :
`$git_cmd config core.pager` || "less";
chomp($less);
open(OUTPUT, "| $less");
print OUTPUT "$package_name: $command{$package_name}{about}\n";
print OUTPUT $self->{'help'};
print OUTPUT "\nDifferences from git $package_name:";
print OUTPUT "\n None.\n" if !defined $self->{'differences'};
print OUTPUT $self->{'differences'} if defined $self->{'differences'};
if ($git_equiv) {
print OUTPUT "\nSee also\n";
print OUTPUT <<EOF;
Run 'man git-$git_equiv' for a comprehensive list of options available.
eg $package_name is designed to accept the same options as git $git_equiv, and
with the same meanings unless specified otherwise in the above
"Differences" section.
EOF
}
close(OUTPUT);
exit 0;
}
sub preprocess {
my $self = shift;
return if (scalar(@ARGV) > 0 && $ARGV[0] eq "--");
my $result=main::GetOptions("--help" => sub { $self->help() });
}
sub run {
my $self = shift;
my $package_name = ref($self);
my $subcommand =
$package_name eq "subcommand" ? $self->{'command'} : $package_name;
@ARGV = Util::quote_args(@ARGV);
return ExecUtil::execute("$git_cmd $subcommand @ARGV", ignore_ret => 1);
}
###########################################################################
# add #
###########################################################################
package add;
@add::ISA = qw(subcommand);
INIT {
$command{add} = {
unmodified_behavior => 1,
section => 'compatibility',
about => 'Mark content in files as being ready for commit'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Description:
eg add is provided for backward compatibility; it has identical usage and
functionality as 'eg stage'. See 'eg help stage' for more details.
";
return $self;
}
###########################################################################
# apply #
###########################################################################
package apply;
@apply::ISA = qw(subcommand);
INIT {
$command{apply} = {
about => 'Apply a patch in a git repository'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 0, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg apply [--staged] [-R | --reverse] [-pNUM]
Description:
Applies a patch to a git repository.
Examples:
Reverse changes in foo.patch
\$ eg apply -R foo.patch
(Advanced) Reverse changes since the last commit to the version of foo.c
in the staging area (equivalent to 'eg revert --staged foo.c'):
\$ eg diff --staged foo.c | eg apply -R --staged
Options:
--staged
Apply the patch to the staged (explicitly marked as ready to be committed)
versions of files
--reverse, -R
Apply the patch in reverse.
-pNUM
Remove NUM leading paths from filenames. For example, with the filename
/home/user/bla/foo.c
using -p0 would leave the name unmodified, using -p1 would yield
home/user/bla/foo.c
and using -p3 would yield
bla/foo.c
";
$self->{'differences'} = '
eg apply is identical to git apply except that it accepts --staged as a
synonym for --cached.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
my $result = main::GetOptions("--help" => sub { $self->help() });
foreach my $i (0..$#ARGV) {
$ARGV[$i] = "--cached" if $ARGV[$i] eq "--staged";
}
}
###########################################################################
# branch #
###########################################################################
package branch;
@branch::ISA = qw(subcommand);
INIT {
$command{branch} = {
section => 'projects',
about => 'List, create, or delete branches'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg branch [-r]
eg branch [-s] NEWBRANCH [STARTPOINT]
eg branch -d BRANCH
Description:
List the existing branches that you can switch to, create a new branch,
or delete an existing branch. For switching the working copy to a
different branch, use the eg switch command instead.
Note that branches are local; creation of branches in a remote repository
can be accomplished by first creating a local branch and then pushing the
new branch to the remote repository using eg push.
Examples
List the available local branches
\$ eg branch
Create a new branch named random_stuff, based off the last commit.
\$ eg branch random_stuff
Create a new branch named sec-48 based off the 4.8 branch
\$ eg branch sec-48 4.8
Delete the branch named bling
\$ eg branch -d bling
Create a new branch named my_fixes in the default remote repository
\$ eg branch my_fixes
\$ eg push --branch my_fixes
(Advanced) Create a new branch named bling, based off the remote tracking
branch of the same name
\$ eg branch bling origin/bling
See 'eg remote' for more details about setting up named remotes and
remote tracking branches, and 'eg help topic storage' for more details on
differences between branches and remote tracking branches.
Options:
-d
Delete specified branch
-r
List remote tracking branches (see 'eg help topic storage') for more
details. This is useful when using named remote repositories (see 'eg
help remote')
-s
After creating the new branch, switch to it
";
$self->{'differences'} = '
eg branch is identical to git branch other than adding a new -s option for
switching to a branch immediately after creating it.
';
return $self;
}
sub run {
my $self = shift;
my $package_name = ref($self);
my $switch = 0;
if (scalar(@ARGV) > 1 && $ARGV[0] eq "-s") {
$switch = 1;
shift @ARGV;
}
@ARGV = Util::quote_args(@ARGV);
my $ret = ExecUtil::execute("$git_cmd branch @ARGV", ignore_ret => 1);
$ret = ExecUtil::execute("$git_cmd checkout $ARGV[0]", ignore_ret => 1)
if ($switch && $ret == 0);
return $ret;
}
###########################################################################
# bundle #
###########################################################################
package bundle;
@bundle::ISA = qw(subcommand);
INIT {
$command{bundle} = {
extra => 1,
section => 'collaboration',
about => 'Pack repository updates (or whole repository) into a file'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
initial_commit_error_msg => "No bundles can be created until a commit " .
"has been made.",
@_
);
bless($self, $class);
$self->{'help'} = "
Usage:
eg bundle create FILENAME [REFERENCES]
eg bundle create-update NEWFILENAME OLDFILENAME [REFERENCES]
eg bundle verify FILENAME
Description:
Bundle creates a file which contains a repository, or a subset thereof.
This is useful when two machines cannot be directly connected (thus
preventing use of the standard interactive git protocols -- git, ssh,
rsync or http), but changes still need to be communicated between the
machines.
The remote side can use the resulting file (or the path to it) as the URL
for the repository they want to clone or pull updates from.
Examples
Create a bundle in the file repo.bundle which contains the whole repository
\$ eg bundle create repo.bundle
After getting the bundle named repo.bundle from a collaborator (which
must contain \"HEAD\" as one of the references if you explicitly list which
ones to be included at creation time), clone the repository into the
directory named project-name
\$ eg clone /path/to/repo.bundle project-name
Create a bundle in the file called new-repo containing only updates since
the bundle old-repo was created.
\$ eg bundle create-update new-repo old-repo
Pulls updates from a new bundle we have been sent.
\$ eg pull /path/to/repo.bundle
Pull updates from a new bundle we have been sent, if we first overwrite
the bundle we originally original cloned from with the new bundle
\$ eg pull
(Advanced) Create a bundle containing the two branches debug and
installer, and the tag named v2.3, in the file called my-changes
\$ eg bundle create my-changes debug installer v2.3
(Advanced) Create a bundle in the file called new-repo that contains
updates since the bundle old-bundle was created, but don't include the
new branch secret-stuff or crazy-idea
\$ eg bundle create-update new-repo old-bundle ^secret-stuff ^crazy-idea
Options:
eg bundle create FILENAME [REFERENCES]
eg bundle create-update NEWFILENAME OLDFILENAME [REFERENCES]
eg bundle verify FILENAME
create FILENAME [REFERENCES]
Create a new bundle in the file FILENAME. If no REFERENCES are passed,
all branches and tags (plus \"HEAD\") will be included. See below for
a basic explanation of REFERENCES.
create-update NEWFILENAME OLDFILENAME [REFERENCES]
Create a new bundle in the file NEWFILENAME, but don't include any
commits already included in OLDFILENAME. See below for a basic
explanation of REFERNCES. By default, any new branch or tags will be
included as well; exclude specific branches or tags by passing ^BRANCH
or ^TAG as a reference; see below for more details.
verify FILENAME
Check whether the given bundle in FILENAME will cleanly apply to the
current repository.
REFERENCES
Which commits to include or exclude from the bundle. Probably best
explained by example:
Example Meaning
----------------- --------------------------------------------------
master Include the master branch
master~10..master Include the last 10 commits on the master branch
^baz foo bar Include commits on the foo or bar branch, except for
those that are in the baz branch
";
$self->{'differences'} = '
eg bundle differs from git bundle in two ways:
(1) eg bundle defaults to "--all HEAD" if no revisions are passed to create
(2) eg bundle provides a create-update subcommand
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
my @args;
my $result=main::GetOptions("--help" => sub { $self->help() });
# Get the (sub)subcommand
$self->{subcommand} = shift @ARGV;
push(@args, $self->{subcommand});
if ($self->{subcommand} eq 'create') {
my $filename = shift @ARGV ||
die "Error: need a filename to write bundle to.\n";
push(@args, $filename); # Handle the filename
if (!@ARGV) {
push(@args, ("--all", "HEAD"));
}
}
elsif ($self->{subcommand} eq 'create-update') {
pop(@args); # 'create-update' isn't a real git bundle subcommand
my $newname = shift @ARGV ||
die "You must specify a new and an old filename.\n";
my $oldname = shift @ARGV ||
die "You must also specify an old filename\n";
die "$oldname does not exist.\n" if ! -f $oldname;
my ($retval, $output) =
ExecUtil::execute_captured("$git_cmd bundle list-heads $oldname");
my @lines = split '\n', $output;
my @refs = map { m#^([0-9a-f]+)# && "^$1" } @lines;
push(@args, ('create', $newname, "--all", "HEAD", @refs));
}
push(@args, @ARGV);
# Reset @ARGV with the built up list of arguments
@ARGV = @args;
}
###########################################################################
# cat #
###########################################################################
package cat;
@cat::ISA = qw(subcommand);
INIT {
$command{cat} = {
new_command => 1,
extra => 1,
section => 'compatibility',
about => 'Output the current or specified version of files'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
git_equivalent => 'show',
initial_commit_error_msg => "Error: Cannot show committed versions of " .
"files when no commits have occurred.",
@_
);
bless($self, $class);
$self->{'help'} = "
Usage:
eg cat [REVISION:]FILE...
Description:
Output the specified file(s) as of the given revisions.
Note that this basically is just a compatibility alias provided for users
of other SCMs. You should consider using 'git show' instead, though with
core git whenever you specify a REVISION, you will need to specify the
path to FILE relative to the toplevel project directory, instead of a
path for FILE relative to the current directory.
Examples
Output the most recently committed version of foo.c
\$ eg cat foo.c
Output the version of bar.h from the 5th to last commit on the
ugly_fixes branch
\$ eg cat ugly_fixes~5:bar.h
Concatenate the version of hello.c from two commits ago and the
version of world.h from the branch timbuktu, and output the result:
\$ eg cat HEAD~1:hello.c timbuktu:world.h
";
$self->{'differences'} = '
The output of "git show FILE" is probably confusing to users at first,
as is the need to specify files relative to the top of the git project
rather than relative to the current directory. Thus, "eg cat FILE"
calls "git show HEAD:PATH/TO/FILE".
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
my $result=main::GetOptions("--help" => sub { $self->help() });
# Get important directories
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
my @args;
foreach my $arg (@ARGV) {
if ($arg !~ /:/) {
my ($path) = Util::reroot_paths__from_to_files($cur_dir, $top_dir, $arg);
push(@args, "HEAD:$path");
} else {
my ($REVISION, $FILE) = split(/:/, $arg, 2);
my ($path) = Util::reroot_paths__from_to_files($cur_dir, $top_dir, $FILE);
push(@args, "$REVISION:$path");
}
}
@ARGV = @args;
}
sub run {
my $self = shift;
my $package_name = ref($self);
@ARGV = Util::quote_args(@ARGV);
return ExecUtil::execute("$git_cmd show @ARGV", ignore_ret => 1);
}
###########################################################################
# changes #
###########################################################################
package changes;
@changes::ISA = qw(subcommand);
INIT {
$command{changes} = {
new_command => 1,
section => 'misc',
about => 'Provide an overview of the changes from git to eg'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, git_equivalent => '', @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg changes [--details]
Options
--details
In addition to the summary of which commands were changed, list the
changes to each command.
";
$self->{'differences'} = '
eg changes is unique to eg; git does not have a similar command.
';
return $self;
}
sub preprocess {
my $self = shift;
$self->{details} = 0;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"--details" => \$self->{details},
);
die "Unrecognized arguments: @ARGV\n" if @ARGV;
}
sub run {
my $self = shift;
my $package_name = ref($self);
if ($debug == 2) {
print " >>(No commands to run, just data to print)<<\n";
return;
}
# Print valid subcommands sorted by section
my $indent = " ";
my $header_indent = "";
$ENV{"LESS"} = "FRSX" unless defined $ENV{"LESS"};
my $less = ($use_pager == 1) ? "less" :
($use_pager == 0) ? "cat" :
`$git_cmd config core.pager` || "less";
chomp($less);
open(OUTPUT, "| $less");
if ($self->{details}) {
print OUTPUT "Summary of changes:\n";
$indent = " ";
$header_indent = " ";
}
print OUTPUT "${header_indent}Modified Behavior:\n";
foreach my $c (sort keys %command) {
next if $command{$c}{unmodified_behavior};
next if $command{$c}{new_command};
print OUTPUT "$indent$c\n";
}
print OUTPUT "${header_indent}New commands:\n";
foreach my $c (sort keys %command) {
next if !$command{$c}{new_command};
print OUTPUT "$indent$c\n";
}
print OUTPUT "${header_indent}Modified Help Only:\n";
foreach my $c (sort keys %command) {
next if $command{$c}{unmodified_help};
next if !$command{$c}{unmodified_behavior};
next if $command{$c}{new_command};
print OUTPUT "$indent$c\n";
}
if ($self->{details}) {
foreach my $c (sort keys %command) {
next if $command{$c}{unmodified_help} || $command{$c}{unmodified_behavior};
$c =~ s/-/_/; # Packages use underscores, commands use dashes
next if !$c->can("new");
my $obj = $c->new(initial_commit_error_msg => '');
print OUTPUT "Changes to $c:\n";
if ($obj->{differences}) {
$obj->{differences} =~ s/^\n//;
print OUTPUT $obj->{differences};
} else {
print OUTPUT " <Unknown>.\n";
}
}
}
close(OUTPUT);
}
###########################################################################
# checkout #
###########################################################################
package checkout;
@checkout::ISA = qw(subcommand);
INIT {
$command{checkout} = {
section => 'compatibility',
about => 'Compatibility wrapper for clone/switch/revert'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 0, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg checkout [-b] BRANCH
eg checkout [REVISION] PATH...
Description:
eg checkout mostly exists as a compatibility wrapper for those used to
other systems (cvs/svn and git). If you:
(1) want a new copy of the source code from a remote repository
OR
(2) want to switch your working copy to a different branch
OR
(3) want to revert the contents of a file to its content from a
different revision
Then use:
(1) eg clone
(2) eg switch
(3) eg revert
eg checkout will accept the same arguments as eg clone (for getting a new
copy of the source code from a remote repository), but will provide an
error message and tell the user to use eg clone in such cases.
The first usage form of eg checkout is used to switch to a different
branch (optionally also creating it first). This is something that can
be done with no network connectivity in git and thus eg. Users can find
identical functionality in eg switch.
The second usage form of eg checkout is used to replace files in the
working copy with versions from an older commit, i.e. to revert files to
an older version. Note that this only works when the specified files
also existed in the older version (eg checkout will not delete or unstage
files for you), does not work for the initial commit (since there's no
older revision to revert back to -- unless you are an advanced user
interested in just undoing the changes since the most recent staging),
and cannot be used to undo an incomplete merge (since it only operates on
a subset of files and not everything since a given commit). Users can
find the same functionality (without all the caveats) as well as other
capabilities in eg revert.
Examples:
Switch to the stable branch
\$ eg checkout stable
Replace foo.c with the third to last version before the most recent
commit (Note that HEAD always refers to the current branch, and the
current branch always refers to its most recent commit)
\$ eg checkout HEAD~3 foo.c
";
$self->{'differences'} = '
eg checkout accepts all parameters that git checkout accepts with the
same meanings and same output (eg checkout merely calls git checkout in
such cases).
The only difference between eg and git regarding checkout is that eg
checkout will also accept all arguments to git clone, and then tell users
that they must have meant to run eg clone (a much nicer error message for
users trying to get a copy of source code from a remote repository than
"fatal: Not a git repository").
';
return $self;
}
sub _looks_like_git_repo {
my $path = shift;
my $clone_protocol = qr#^(?:git|ssh|http|https|rsync)://#;
my $git_dir = RepoUtil::git_dir();
my $in_working_copy = defined $git_dir ? 1 : 0;
# If the path looks like a git, ssh, http, https, or rsync url, then it
# looks like we're being given a url to a git repo
if ($path =~ /$clone_protocol/) {
return 1;
}
# If the path isn't a clone_protocol url and isn't a directory, it can't be
# a git repo
if (! -d $path) {
return 0;
}
my $path_is_absolute = ($path =~ m#^/#);
return (!$in_working_copy || ($in_working_copy && $path_is_absolute));
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
if (scalar(@ARGV) > 0 && $ARGV[0] ne "--") {
main::GetOptions("--help" => sub { $self->help() });
}
$self->{command} = 'checkout';
die "eg checkout requires at least one argument.\n" if !@ARGV;
#
# Determine whether this should be a call to git clone or git checkout
#
my $clone_protocol = qr#^(?:git|ssh|http|https|rsync)://#;
if (_looks_like_git_repo($ARGV[-1]) ||
(! -d $ARGV[-1] && @ARGV > 1 && _looks_like_git_repo($ARGV[-2]))
) {
$self->{command} = 'clone';
}
}
sub run {
my $self = shift;
@ARGV = Util::quote_args(@ARGV);
if ($self->{command} ne 'clone') {
# If this operation isn't a clone, then we should have checked for
# whether we are in a git directory. But we didn't do that, just in
# case it was a clone. So, do it now.
$self->{git_dir} = RepoUtil::git_dir();
die "Must be run inside a git repository!\n" if !defined $self->{git_dir};
return ExecUtil::execute("$git_cmd checkout @ARGV", ignore_ret => 1);
} else {
die "Did you mean to run\n eg clone @ARGV\n?\n";
}
}
###########################################################################
# cherry_pick #
###########################################################################
package cherry_pick;
@cherry_pick::ISA = qw(subcommand);
INIT {
$command{"cherry-pick"} = {
extra => 1,
section => 'modification',
about => 'Apply (or reverse) a commit, usually from another branch'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg cherry-pick [--reverse] [--edit] [-n] [-m parent-number] [-s] [-x] REVISION
Description:
Given an existing revision, apply the change between its parent and it
(or reverse apply if the --reverse option is present), and record a new
revision with this change. Your working tree must be clean (no local
unsaved modifications) in order to run eg cherry-pick.
Examples:
Apply the fix 3 commits behind the tip of the experimental branch
(i.e. the fix made in experimental~3) to the current branch
\$ eg cherry-pick experimental~3
Make a new commit that reverses the changes made in the most recent
commit of the current branch
\$ eg cherry-pick -R HEAD
Options:
--reverse, --revert, -R
Reverse apply the changes from the specified commit (i.e. revert the
specified revision with a new commit).
--edit, -e
With this option, eg cherry-pick will let you edit the commit message
prior to committing.
-x
When recording the commit, append to the original commit message a note
that indicates which commit this change was cherry-picked from. Append
the note only for cherry picks without conflicts. Do not use this
option if you are cherry-picking from your private branch because the
information is useless to the recipient. If on the other hand you are
cherry-picking between two publicly visible branches (e.g. backporting
a fix to a maintenance branch for an older release from a development
branch), adding this information can be useful.
This option is turned on automatically when -R is specified.
--mainline parent-number, -m parent-number
cherry-pick always applies the changes between a revision and its
parent; thus if a revision represents a merge commit, it is not clear
which parent cherry-pick should get the changes relative to. This
option specifies the parent number (starting from 1) of the mainline
and allows cherry-pick to replay the change relative to the specified
parent.
--no-commit, -n
Usually cherry-pick automatically creates a commit. This flag applies
the change necessary to cherry-pick the named revision to your working
tree and staging area, but does not make the commit. In addition, when
this option is used, the staging area can contain unsaved changes and
the cherry-pick will be done against the beginning state of your
staging area.
This is useful when cherry-picking more than one commit into a single
combined change.
--signoff, -s
Add Signed-off-by line at the end of the commit message.
REVISION
A reference to a recorded version of the repository. See 'eg help
topic revisions' for more details.
";
$self->{'differences'} = '
eg cherry-pick contains both the functionality of git cherry-pick and git
revert. If the -R option is specified, eg cherry-pick calls git revert
(after removing the -R option); otherwise it calls git cherry-pick.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
my ($reverse, $dash_x, $mainline) = (0, 0, -1);
Getopt::Long::Configure("permute"); # Allow unrecognized options through
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"mainline|m=i" => \$mainline,
"reverse|R" => \$reverse,
"revert" => \$reverse,
"x" => \$dash_x,
);
$self->{reverse} = $reverse;
unshift(@ARGV, "-x") if (!$reverse && $dash_x);
unshift(@ARGV, ("-m", $mainline)) if $mainline != -1;
}
sub run {
my $self = shift;
@ARGV = Util::quote_args(@ARGV);
if ($self->{reverse}) {
return ExecUtil::execute("$git_cmd revert @ARGV", ignore_ret => 1);
} else {
return ExecUtil::execute("$git_cmd cherry-pick @ARGV", ignore_ret => 1);
}
}
###########################################################################
# clone #
###########################################################################
package clone;
@clone::ISA = qw(subcommand);
INIT {
$command{clone} = {
section => 'creation',
about => 'Clone a repository into a new directory'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 0, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg clone [--depth DEPTH] REPOSITORY [DIRECTORY]
Description:
Obtains a copy of a remote repository, including all history by default.
A --depth option can be passed to only include a specified number of
recent commits instead of all history (however, this option exists mostly
due to the fact that users of other SCMs fail to understand that all
history can be compressed into a size that is often smaller than the
working copy).
See 'eg help topic remote-urls' for a detailed list of the different ways
to refer to remote repositories.
Examples:
Get a local clone of cairo
\$ eg clone git://git.cairographics.org/git/cairo
Get a clone of a local project in a new directory 'mycopy'
\$ eg clone /path/to/existing/repo mycopy
Get a clone of a project hosted on someone's website, asking for only the
most recent 20 commits instead of all history, and storing it in the
local directory mydir
\$ eg clone --depth 20 http://www.random.machine/path/to/git.repo mydir
Options:
--depth DEPTH
Only download the DEPTH most recent commits instead of all history
";
$self->{'differences'} = "
eg clone and git clone are very similar, but eg clone by default sets up
a branch for each remote branch automatically (instead of only creating
one branch, typically master).
";
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
$self->{bare} = 0;
my @old_argv = @ARGV;
Getopt::Long::Configure("permute");
my $result = main::GetOptions(
"help" => sub { $self->help() },
"bare" => sub { $self->{bare} = 1 },
"depth=i",
"origin|o=s",
"reference=s",
"upload-pack|u=s",
);
shift @ARGV while ($ARGV[0] =~ /^-/); # Skip past any other options
$self->{repository} = shift @ARGV;
die "No repository specified!\n" if !$self->{repository};
my $basename = $self->{repository};
$basename =~ s#/*$##; # Remove trailing slashes
$basename =~ s#.*[/:]##; # Remove everything but final dirname
$basename =~ s#\.git$##; # Remote .git suffix, if present
$basename =~ s#\.bundle$##; # Remote .bundle suffix, if present
$self->{directory} = shift @ARGV || $basename . ($self->{bare} ? ".git" : "");
die "Too many parameters to clone!\n" if (scalar(@ARGV) > 0);
@ARGV = @old_argv; # Workaround: GetOptions may have stripped a leading '--'
}
sub run {
my $self = shift;
#
# Perform the clone
#
@ARGV = Util::quote_args(@ARGV);
my $ret = ExecUtil::execute("$git_cmd clone @ARGV", ignore_ret => 1);
return $ret if $self->{bare};
if ($debug == 2) {
return if $self->{bare};
print " >>Running: 'cd $self->{directory}'<<\n";
print " >>Running: '$git_cmd branch -r'<<\n";
print " --- Setting up extra branches by default ---\n";
print " >>Running, for each remote branch besides master (referred to as BRANCH):\n";
print " $git_cmd branch BRANCH origin/BRANCH\n";
} elsif ($ret == 0) {
# Switch to the appropriate directory, remembering the repository we
# checked out
die "$self->{directory} does not exist after checkout!"
if ! -d $self->{directory};
$self->{repository} = main::abs_path($self->{repository})
if -d $self->{repository};
chdir($self->{directory});
# Determine local and remote branches
my @remote_branches =
split('\n', `$git_cmd for-each-ref --format '%(refname)' refs/remotes`);
@remote_branches = map { m#^refs/remotes/(.*)$# && $1 } @remote_branches;
my @local_branches =
split('\n', `$git_cmd for-each-ref --format '%(refname)' refs/heads`);
@local_branches = map { m#^refs/heads/(.*)$# && $1 } @local_branches;
# Set branch.@local_branches.rebase to true if branch.autosetuprebse is true
my $autosetuprebase = `$git_cmd config --global branch.autosetuprebase`;
chomp($autosetuprebase);
if ($autosetuprebase eq 'always' || $autosetuprebase eq 'remote') {
foreach my $branch (@local_branches) {
ExecUtil::execute("$git_cmd config branch.$branch.rebase true");
}
}
# Set up a branch for each remote branch, not just master
foreach my $b (@remote_branches) {
($remote, $branch) = ($b =~ m#^(.*)/(.*?)$#);
next if $branch eq "HEAD";
next if grep {$branch eq $_} @local_branches;
ExecUtil::execute("$git_cmd branch $branch $remote/$branch > /dev/null");
}
}
return $ret;
}
###########################################################################
# commit #
###########################################################################
package commit;
@commit::ISA = qw(subcommand);
INIT {
$command{commit} = {
section => 'modification',
about => 'Record changes locally'
};
$alias{'checkin'} = "commit";
$alias{'ci'} = "commit";
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg commit [-a|--all-known] [-b|--bypass-unknown-check]
[--staged|-d|--dirty] [-F FILE | -m MSG | --amend] [--]
[FILE...]
Description:
Records changes locally along with a log message describing the
changes you have made. If no -F or -m option is supplied, an editor
is opened for you to enter a log message.
In order to prevent common errors, the commit will abort with a warning
message if there are no changes to commit, there are conflicts from a
merge, or if eg detects that the choice of what to commit is ambiguous.
In particular, if you have any \"newly created\" unknown files present,
or if you have both staged changes (i.e. changes explicitly marked as
ready for commit) and unstaged changes, then you will get a warning
rather than having the commit occur. You can run 'eg status' to get the
status of various files and their changes. These commit checks can be
bypassed with various options.
Examples:
Record current changes locally, not changing anything in CVS...OR...get
a warning message if eg detects that the choice of what to commit is not
necessarily clear.
\$ eg commit
Record current changes, ignoring any unknown files present. Also
remember the list of unknown files so that their existence will not
trigger future \"You have new unknown files present\" warnings when not
using the -b flag.
\$ eg commit -b
Record brand new file and current changes.
\$ eg stage file.c
\$ eg commit -a
Note: Running 'eg stage FILE' explicitly marks FILE as being ready to
commit. Since you likely haven't explicitly marked your other changes as
ready to commit, pass the -a flag to specify that both kinds of changes
should be recorded.
(Advanced) Record staged changes, ignoring both unstaged changes and
unknown files.
\$ eg commit --staged
Options:
--all-known, -a
(Could also be called --act-like-other-vcses). Commit both staged
(i.e. explictly marked as ready for commit) changes and unstaged
changes.
Incompatible with explicitly specifying files to commit on the command
line, and incompatible with the --staged option.
--bypass-unknown-check, -b
Commit local changes, even if there are unknown files around. If this
flag is not used and unknown files are currently present that were not
present the last time the -b flag was used, then the commit will be
aborted with a warning message.
--staged, --dirty, -d
Commit only staged changes and bypass sanity checks. (\"dirty\" is kept
as a synonym in order to provide a short (-d) form. The term \"dirty\"
is used to convey the fact that the working area will likely not be
\"clean\" after a commit since unstaged changes will still be present).
WARNING: Do not try to use -s as a shorthand for --staged; -s has a
different meaning (see 'git commit --help')
Incompatible with explicitly specifying files to commit on the command
line, and incompatible with the --all-known option.
-F FILE
Use the contents of FILE as the commit message
-m MSG
Use MSG as the commit message.
--amend
Amend the last commit on the current branch.
";
$self->{'differences'} = '
The "--staged" (and "-d" and "--dirty" aliases) are unique to eg commit;
git commit behavior differs from eg commit in that it acts by default
like the --staged flag was passed UNLESS either the -a option is passed
or files are explicitly listed on the command line.
The "--bypass-unknown-check" is unique to eg commit; git commit
behavior differs by always turning on this functionality -- there is
no way to have git commit do an unknown files sanity check for you.
"-a" is not nearly as useful for eg commit as it is for git commit. "-a"
has the same behavior in both, but the "smart" behavior of eg commit
means it is only rarely needed.
The "--all-known" alias for "-a" is known as "--all" to git-commit; I
find the latter confusing and misleading and thus renamed to the former
for eg commit.
To be precise about the behavior of a plain "eg commit":
If the working copy is clean -> warn user: nothing to commit
else if there are unmerged files -> warn user: unmerged files
else if there are new untracked files -> warn user: new unknown files
else if both "staged" & unstaged changes[1] -> warn user: mix
else -> run "git commit -a"
Actually, I do not pass -a if there are only staged changes present, but
the result is the same. Note that this essentially boils down to making
the user do less work (no need to remember -a in the common case) and
extending the sanity checks git commit does (which currently only covers
the clean working copy case) to also prevent a number of other very
common user pitfalls.
[1] The reason for putting "staged" in quotes comes from the case of
running "eg commit --amend" when you have local unstaged changes. Does
the user want to merely amend the prior commit message or add their
changes to the previous commit? (Even if the index matches HEAD at this
time, we are committing relative to HEAD^1.) It is not clear what the
user wants, so we warn and ask them to use -a or --staged.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
$self->{args} = [];
my $record_arg = sub { push(@{$self->{args}}, "$_[0]$_[1]"); };
my $record_args = sub { push(@{$self->{args}}, "$_[0]$_[1]");
push(@{$self->{args}}, splice(@_, 2)); };
my ($all_known, $bypass_unknown, $staged, $amend) = (0, 0, 0, 0);
Getopt::Long::Configure("permute");
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"all-known|a" => \$all_known,
"bypass-unknown-check|b" => \$bypass_unknown,
"staged|dirty|d" => \$staged,
"s" => sub { &$record_arg("-", @_) },
"v" => sub { &$record_arg("-", @_) },
"u" => sub { &$record_arg("-", @_) },
"c=s" => sub { &$record_args("-", @_) },
"C=s" => sub { &$record_args("-", @_) },
"F=s" => sub { &$record_args("-", @_) },
"file=s" => sub { &$record_args("--", @_) },
"m=s" => sub { &$record_args("-", @_) },
"amend" => sub { $amend = 1; &$record_arg("--", @_) },
"allow-empty" => sub { &$record_arg("--", @_) },
"no-verify" => sub { &$record_arg("--", @_) },
"e" => sub { &$record_arg("-", @_) },
"author=s" => sub { &$record_args("--", @_) },
"cleanup=s" => sub { &$record_args("--", @_) },
);
my ($opts, $revs, $files) = RepoUtil::parse_args(@ARGV);
#
# Set up flags based on options, do sanity checking of options
#
my ($check_unknown, $check_mixed, $check_no_branch);
$self->{'commit_flags'} = [];
die "Cannot specify both --all-known (-a) and --staged (-d)!\n" if
$all_known && $staged;
die "Cannot specify --staged when specifying files!\n" if @$files && $staged;
$check_unknown = !$bypass_unknown && !$staged && !@$files;
$check_mixed = !$all_known && !$staged && !@$files;
push(@{$self->{'commit_flags'}}, "-a") if $all_known;
#
# Lots of sanity checks
#
my $status =
RepoUtil::commit_push_checks($package_name,
{no_changes => 1,
unknown => $check_unknown,
partially_staged => $check_mixed});
if ($amend && !$all_known && !$staged && !@$files &&
$status->{has_unstaged_changes} && !$status->{has_staged_changes}) {
print STDERR <<EOF;
Aborting: It is not clear whether you want to simply amend the commit
message or whether you want to include your local changes in the amended
commit. Please pass --staged to just amend the previous commit message, or
pass -a to include your current local changes with the previous amended
commit.
EOF
exit 1;
}
die "No staged changes, but --staged given.\n"
if (!$status->{has_staged_changes} && $staged && !$amend);
if (!$all_known && !$staged &&
$status->{has_unstaged_changes} && !$status->{has_staged_changes} &&
!@$files) {
push(@{$self->{'commit_flags'}}, "-a");
}
#
# Record the set of unknown files we ignored with -b, so the -b flag isn't
# needed next time.
#
if ($bypass_unknown) {
RepoUtil::record_ignored_unknowns();
}
push(@{$self->{args}}, @{$self->{commit_flags}});
unshift(@ARGV, @{$self->{args}});
}
###########################################################################
# config #
###########################################################################
package config;
@config::ISA = qw(subcommand);
INIT {
$command{config} = {
unmodified_behavior => 1,
extra => 1,
section => 'misc',
about => 'Get or set configuration options'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 0, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg config OPTION [ VALUE ]
eg config --unset OPTION
eg config [ --list ]
Description:
Gets or sets configuration options.
See the 'Configuration File' section of 'man git-config' for a fairly
comprehensive list of special options used by eg (and git).
Examples:
Get the value of the configuration option user.email
\$ eg config user.email
Set the value of the configuration option user.email to whizbang\@flashy.org
\$ eg config user.email whizbang\@flashy.org
Unset the values of the configuration options branch.master.remote
and branch.master.merge
\$ eg config --unset branch.master.remote
\$ eg config --unset branch.master.merge
List all options that have been set
\$ eg config --list
";
return $self;
}
###########################################################################
# diff #
###########################################################################
package diff;
@diff::ISA = qw(subcommand);
INIT {
$command{diff} = {
section => 'discovery',
about => 'Show changes to file contents'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg diff [--unstaged | --staged] [REVISION] [REVISION] [FILE...]
Description:
Shows differences between different versions of the project. By default,
it shows the differences between the last locally recorded version and the
version in the working copy.
Examples:
Show local unrecorded changes
\$ eg diff
In a project with the current branch being 'master', show the differences
between the version before the last recorded commit and the working copy.
\$ eg diff master~1
Or do the same using \"HEAD\" which is a synonym for the current branch:
\$ eg diff HEAD~1
Show changes to the file myscript.py between 10 versions before last
recorded commit and the last recorded commit (assumes the current branch
is 'master').
\$ eg diff master~10 master myscript.py
(Advanced) Show changes between staged (ready-to-be-committed) version of
files and the working copy (use 'eg stage' to stage files). In other
words, show the unstaged changes.
\$ eg diff --unstaged
(Advanced) Show changes between last recorded copy and the staged (ready-
to-be-committed) version of files (use 'eg stage' to stage files). In
other words, show the staged changes.
\$ eg diff --staged
(Advanced) Show changes between 5 versions before the last recorded
commit and the currently staged (ready-to-be-committed) version of the
repository. (Use 'eg stage' to stage files).
\$ eg diff --staged HEAD~5
Options:
REVISION
A reference to a recorded version of the repository, defaulting to HEAD
(meaning the most recent commit on the current branch). See 'eg help
topic revisions' for more details.
--staged
Show changes between the last commit and the staged copy of files.
Cannot be used when two revisions have been specified.
--unstaged
Show changes between the staged copy of files and the current working
directory. Cannot be used when a revision is specified.
";
$self->{'differences'} = '
Changes to eg diff relative to git diff are:
(1) Different defaults for what to diff relative to
(2) Providing a more consistent double-dot operator
Section 1: Different defaults for what to diff relative to
The following illustrate the two changed defaults of eg diff:
eg diff <=> git diff HEAD
eg diff --unstaged <=> git diff
(Which is not 100% accurate due to merges; see below.) In more detail:
The "--unstaged" option is unique to eg diff; to get the same behavior
with git diff you simply list no revisions and omit the "--cached" flag.
When neither --staged nor --unstaged are specified to eg diff and no
revisions are given, eg diff will pass along the revision "HEAD" to git
diff.
The "--staged" option is an alias for "--cached" unique to eg diff; the
purpose of the alias is to reduce the number of different names in git
used to refer to the same concept. (Update: the --staged flag is now
part of git with the same meaning as in eg.)
Merges: The above is slightly modified if the user has an incomplete
merge; if the user has conflicts during a merge (or uses --no-commit when
calling merge) and then tries "eg diff", it will abort with a message
telling the user that there is no "last" commit and will provide
alternative suggestions.
Section 2: Providing a more consistent double-dot operator
The .. operator of git diff (e.g. git diff master..devel) means what
the ... operator of git log means, and vice-versa. This causes lots of
confusion. We fix this by aliasing making the .. operator of eg diff
do exactly what the ... operator of git diff does. To see why:
Meanings of git commands, as a reminder (A and B are revisions):
git diff A..B <=> git diff A B # Endpoint difference
git diff A...B <=> git diff $(git merge-base A B) B
Why this is confusing (compare to above):
git log A..B <=> git log $(git merge-base A B) B
git log A...B <=> git log ^$(git merge-base A B) A B # Endpoint difference
So, my translation:
eg diff A B <=> git diff A B <=> git diff A..B
eg diff A..B <=> git diff A...B
eg diff A...B <=> git diff A...B
Reasons for this change:
* New users automatically get sane behavior, and use either eg diff A B
or eg diff A..B, each doing what one would expect. They do not ever
realize that A...B is a bit weird because they have no need to try to
use it; eg diff A B covers their needs.
* Users worried about switching between eg and git without having to
modify their command lines can always use either diff A B or
diff A...B, but never any other form; using this subset ensures that
both eg and git behave identically.
* Users only access git diff A..B behavior through eg diff A B, which
is less typing and makes more sense.
* Since git diff A..B and git diff A B are the same, the latter is far
more common, and the former is confusing, odds are that if any git
user suggests someone use git diff A..B they probably really meant
git diff A...B
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
# Avoid Util::git_rev_parse because it fails on t2010-checkout-ambiguous by
# treating "--quiet" as a revision rather than an option; use our own
# parse_args implementation instead.
my ($opts, $revs, $files) = RepoUtil::parse_args(@ARGV);
# Replace '..' with '...' in revision specifiers. Use backslash escaping to
# get actual dots and not just any character. Use negative lookbehind and
# lookahead assertions to avoid replacing '...' with '....'.
my @new_revs = map(m#(.+)(?<!\.)\.\.(?!\.)(.+)# ? "$1...$2" : $_, @$revs);
$revs = \@new_revs;
#
# Parse options
#
$self->{'opts'} = "";
@ARGV = @$opts;
my ($staged, $unstaged, $no_index) = (0, 0, 0);
Getopt::Long::Configure("permute");
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"staged|cached" => \$staged,
"unstaged" => \$unstaged,
"no-index" => \$no_index,
);
die "Cannot specify both --staged and --unstaged!\n" if $staged && $unstaged;
my @args;
push(@args, "--cached") if $staged;
push(@args, "--no-index") if $no_index;
push(@args, @ARGV);
#
# Parse revs
#
die "eg diff: Cannot specify '--staged' with more than 1 revision.\n"
if ($staged && scalar @$revs > 1);
die "eg diff: Cannot specify '--unstaged' with any revisions.\n"
if ($unstaged && scalar @$revs > 0);
# 'eg diff' (without arguments) should act like 'git diff HEAD', unless
# we are in an aborted merge state
if (!@$revs && !$unstaged && !$staged && !$no_index) {
if (-f "$self->{git_dir}/MERGE_HEAD") {
my @merge_branches = RepoUtil::merge_branches();
my $list = join(", ", @merge_branches);
print STDERR <<EOF;
Aborting: Cannot show the changes since the last commit, since you are in the
middle of a merge and there are multiple last commits. Try passing one of
--unstaged, $list
to eg diff.
For additional conflict resolution help, try eg log --merge or
eg show BRANCH:FILE
where FILE is any file in your working copy and BRANCH is one of
$list
EOF
exit 1;
}
push(@$revs, "HEAD")
}
push(@args, @$revs);
push(@args, "--");
push(@args, @$files);
@ARGV = @args;
}
###########################################################################
# gc #
###########################################################################
package gc;
@gc::ISA = qw(subcommand);
INIT {
$command{gc} = {
unmodified_behavior => 1,
extra => 1,
section => 'timesavers',
about => 'Optimize the local repository to make later operations faster',
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg gc
Description:
Optimizes the local repository; in particular, this command compresses
file revisions to reduce disk space and increase performance.
This command is occasionally called during normal git usage, making
explicit usage of this command unnecessary for many users. However, the
automatic calls of this command only do simple and quick optimizations,
so some users (particularly those with many revisions) may benefit from
manually invoking this command periodically (such as from nightly or
weekly cron scripts).
";
return $self;
}
###########################################################################
# help #
###########################################################################
package help;
@help::ISA = qw(subcommand);
INIT {
$command{help} = {
section => 'misc',
about => 'Get command syntax and examples'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(exit_status => 0,
git_equivalent => '',
git_repo_needed => 0,
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg help --all
eg help [COMMAND]
eg help topic [TOPIC]
Description:
Shows general help for eg, for one of its subcommands, or for a
specialized topic.
Examples:
Show help for eg
\$ eg help
Show help for extended list of commands available in eg
\$ eg help --all
Show help for the switch command of eg
\$ eg help switch
Show which topics have available help
\$ eg help topic
Show the help for the staging topic
\$ eg help topic staging
";
$self->{'differences'} = "
eg help uses its own help system, ignoring the one from git help...except
that eg help will call git help if asked for help on a subcommand it does
not recognize.
'git help COMMAND', by default, simply calls 'man git-COMMAND'. The git
man pages are really nice for people who are experts with git; they are
comprehensive and detailed. However, new users tend to get lost in a sea
of details and advanced topics (among other problems). 'eg help COMMAND'
provides much simpler pages of its own and refers to the manpages for
more details. The eg help pages also list any differences between the eg
commands and the git ones, to assist interested users in learning git.
If you simply want a brief list of available options and descriptions,
you may also want to try running 'git COMMAND -h' (which differs from
the two identical commands 'git COMMAND --help' and 'git help COMMAND').
";
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
$self->{all} = 0;
my $result=main::GetOptions("--help" => sub { $self->help() },
"--all" => \$self->{all});
}
sub run {
my $self = shift;
my $package_name = ref($self);
if ($debug == 2) {
print " >>(No commands to run, just data to print)<<\n";
return;
}
# Check if we were asked to get help on a subtopic rather than toplevel help
if (@ARGV > 0) {
my $subcommand = shift @ARGV;
$subcommand =~ s/-/_/; # Packages use underscores, commands use dashes
if (@ARGV != 0 && ($subcommand ne 'topic' || @ARGV != 1)) {
die "Too many arguments to help.\n";
}
die "Oops, there's a bug.\n" if $self->{exit_status} != 0;
$subcommand = "help::topic" if $subcommand eq 'topic';
if (!$subcommand->can("new")) {
print "$subcommand is not modified by eg (eg $subcommand is equivalent" .
" to git $subcommand).\nWill try running 'git help $subcommand'" .
" in 2 seconds...\n";
sleep 2;
exit ExecUtil::execute("$git_cmd help $subcommand");
}
my $subcommand_obj = $subcommand->new(initial_commit_error_msg => '',
git_repo_needed => 0);
$subcommand_obj->help();
}
# Set up a pager, if wanted
$ENV{"LESS"} = "FRSX" unless defined $ENV{"LESS"};
my $less = ($use_pager == 1) ? "less" :
($use_pager == 0) ? "cat" :
`$git_cmd config core.pager` || "less";
chomp($less);
open(OUTPUT, "| $less");
# Help users know about the --all switch
if (!$self->{all}) {
print OUTPUT "(Run 'eg help --all' for a more detailed list.)\n\n";
}
# Print valid subcommands sorted by section
foreach my $name (sort
{$section->{$a}{'order'} <=> $section->{$b}{'order'}}
keys %$section) {
next if $section->{$name}{extra} && !$self->{all};
print OUTPUT "$section->{$name}{desc}\n";
foreach my $c (sort keys %command) {
next if !defined $command{$c}{section};
next if $command{$c}{section} ne $name;
next if $command{$c}{extra} && !$self->{all};
printf OUTPUT " eg %-11s %s\n", $c, $command{$c}{about};
}
print OUTPUT "\n";
}
# Check to see if someone added a command with an invalid section
my $broken_commands = "";
foreach my $c (keys %command) {
next if !defined $command{$c}{section};
next if defined $section->{$command{$c}{section}};
my $tmp = sprintf(" eg %-10s %s\n", $c, $command{$c}{about});
$broken_commands .= $tmp;
}
if ($broken_commands) {
print OUTPUT "Broken (typo in classification?) commands:\n" .
"$broken_commands\n";
}
# And let them know how to get more detailed help...
print OUTPUT "Additional help:\n";
print OUTPUT " eg help COMMAND Get more help on COMMAND.\n";
print OUTPUT " eg help --all List more commands (not really all)\n";
print OUTPUT " eg help topic List specialized help topics.\n";
# And let them know how to compare to git
if ($self->{all}) {
print OUTPUT "\n";
print OUTPUT "Learning or comparing to git\n";
print OUTPUT " eg --translate ARGS Show commands that would be executed for 'eg ARGS'\n";
print OUTPUT " eg --debug ARGS Show & run commands that would be executed by 'eg ARGS'\n";
}
close(OUTPUT);
exit $self->{exit_status};
}
###########################################################################
# help::topic #
###########################################################################
package help::topic;
sub new {
my $class = shift;
my $self = {};
bless($self, $class);
return $self;
}
sub middle_of_am {
my $continue_text = "
1. Standard case
When all conflicts have been resolved, run
eg am --resolved
Do NOT run \"eg commit\" to continue an interrupted rebase (unless you want
to manually insert a new commit; if you already accidentally ran eg commit,
then run 'eg reset HEAD~1' to undo it). If you try to continue without
resolving all conflicts, the command will error out and tell you that some
conflicts remain to be resolved.
2. Special case -- skipping a commit
If you do not want this particular commit to be included in the final
result, run
eg am --skip";
my $abort_text = "
To abort your rebase operation, simply run
eg am --abort";
return _conflict_resolution_message(op => "am",
show_empty_case => 1,
continue_text => $continue_text,
abort_text => $abort_text
);
}
sub middle_of_merge {
my $completion_text = "
When all conflicts have been resolved, run
eg commit
The log message will be pre-populated with a sample commit message for you,
noting the merge and any file conflicts. If you try to run this command
without resolving all conflicts, the command will error out and tell you
that some conflicts remain to be resolved.";
my $abort_text = "
If you had no uncommitted changes before the merge (or do not care about
keeping those changes), you can run
eg reset --working-copy ORIG_HEAD
If you had uncommitted changes before starting the merge, and have
git-1.6.2 or later, you can try
eg ls-files --unmerged | awk {'print \$4'} | uniq | xargs eg stage
eg reset --merge ORIG_HEAD
The first command will mark all unmerged files as ready for commit (who
doesn't like having conflict markers in their files?), and the second
command undoes all changes to staged files since ORIG_HEAD -- both the
files that were successfully merged by git, and the files that you manually
staged in the first command.";
return _conflict_resolution_message(op => "merge",
show_empty_case => 0,
continue_text => $completion_text,
abort_text => $abort_text);
}
sub middle_of_rebase {
my $extra_stop_info = "
If you are in the middle of an interactive rebase (i.e. you specified the
--interactive flag), then rebase can also stop if you selected to edit a
commit, even when there are no conflicts. In such a case where there are
no conflicts, there is something else you may want to do:
5) Editing during an interactive rebase
Before telling git to continue the operation.";
my $continue_text = "
1. Standard case
When all conflicts have been resolved, run
eg rebase --continue
Do NOT run \"eg commit\" to continue an interrupted rebase (unless you want
to manually insert a new commit; if you already accidentally ran eg commit,
then run 'eg reset HEAD~1' to undo it). If you try to continue without
resolving all conflicts, the command will error out and tell you that some
conflicts remain to be resolved.
2. Special case -- skipping a commit
If you do not want this particular commit to be included in the final
result, run
eg rebase --skip";
my $abort_text = "
To abort your rebase operation, simply run
eg rebase --abort";
my $interactive_edit_text = "
******************* Editing during an interactive rebase *******************
When an interactive rebase stops to allow you to edit a commit, make any
necessary changes to files, then run
eg commit --amend
If you do not use the --amend flag, you will be inserting a new commit
after the one you chose to edit.
After you are done amending the previous commit (and/or commit message),
run 'eg rebase --continue' to allow the rebase operation to continue.
";
return _conflict_resolution_message(op => "rebase",
show_empty_case => 1,
extra_stop_info => $extra_stop_info,
continue_text => $continue_text,
abort_text => $abort_text,
final_text => $interactive_edit_text,
);
}
sub _conflict_resolution_message {
my $opts = {op => "!!!FIXME!!!",
show_empty_case => 0,
extra_stop_info => '',
continue_text => '!!!FIXME!!!',
abort_text => '!!!FIXME!!!',
final_text => '',
@_}; # Hashref initialized as we're told
my $result = "
When conflicting changes are detected, a $opts->{op} operation will stop to
allow a user to resolve the conflicts. At this stage there is one of four
things a user may want to do:
1) Find out more about what conflicts occurred
2) Resolve the conflicts
3) Tell git to complete the operation
4) Abort the operation
Each will be discussed below.$opts->{extra_stop_info}
*************** Find out more about what conflicts occurred ***************
1. Standard case
In order to find out which files have conflicts, run
eg status
and then look for lines that begin with \"unmerged:\". You can then open
the relevant file in an editor and look for lines with conflict markers,
i.e. lines that start with one of
<<<<<<<
=======
>>>>>>>
Between the <'s and the ='s will be one version of the changed file, while
betwen the ='s and the >'s will be another version.
2. Simple tip
Since git will stage any changes it is able to successfully merge, you can
find the unresolved conflict sections of a file by running
eg diff --unstaged FILE
";
if ($opts->{show_empty_case}) {
$result .= "
3. Special empty commit case
Sometimes, during a $opts->{op}, the changes in a commit will no longer be
necessary since they have already been included in the code which your
commit is being applied on top of. In such a case, eg status will simply
show that there are no changes at all, and you can continue by telling git
to skip the current unneeded commit (see below).
4. Difficult cases
"
} else {
$result .= "
3. Difficult cases
";
}
$result .= "
You can run
eg ls-files --unmerged
to get a list of all files in the unmerged state. This will list up to
three lines for each file, and look like the following:
100644 45b983be36b73c0788dc9cbcb76cbb80fc7bb057 1 foo.C
100644 ce013625030ba8dba906f756967f9e9ca394464a 2 foo.C
100644 dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 3 foo.C
The first line corresponds to a version of the file at some common point in
history, the second and third lines correspond to different versions of the
file being merged (relative to the common version). Each line is of the
form
mode internal-object-name stage filename
The mode represents the permission bits and or type (executable file,
symlink etc.), the internal-object-name is git's internal name for the
contents of the file, the stage is a simple integer, and you should
recognize the filename.
You can make use of this information to detect the following situations:
A) There's a conflict in mode change (e.g. removed the executable bit on
one side of history, turned the file into a symlink in another)
B) The file is deleted in one version and modified in another (when this
happens either the 2nd or 3rd stage line will be missing)
Further, you can view the different versions of the file easily, by using
either of:
eg show :STAGE:FILENAME
eg show INTERNAL-OBJECT-NAME
Some examples using the output above:
eg show :2:foo.C
eg show dd7e1c6f0fefe118f0b63d9f10908c460aa317a6
************************** Resolve the conflicts **************************
1. Standard case.
For each file with conflicts, edit the file to remove the conflict markers
and provide just the correct version of the merged file. Then run
eg stage FILE
to tell git that you have resolved the conflicts in FILE.
2. Special cases
Nearly all special cases (and even the standard case) boil down to making
sure the file has the correct contents, the correct permission bits and
type, and then running
eg stage FILE
If the file is a binary, then there will not be any conflict markers. In
such a case, simply ensure that the contents of the file are what you want
and then run eg stage, as noted above.
If the file is deleted on one side of history and changed in another,
decide what contents the file should have. If the correct resolution is to
delete the file, run
eg rm FILE
Otherwise, put the appropriate contents in the file and run eg stage as
noted above.
If the file has a mode conflict, then fix up the mode of the file (run
'man chmod' and 'man ln' for help on how to do so). Note that the modes
used by git are as follows:
100644 -- Normal, non-executable file
100755 -- File with the executable bit set
120000 -- symlink
160000 -- A git submodule (run 'man git-submodule' for more info)
******************** Tell git to continue the operation ********************
$opts->{continue_text}
*************************** Abort the operation ***************************
$opts->{abort_text}
";
$result .= $opts->{final_text};
return $result;
}
sub middle_of_bisect {
return "
When git is bisecting, it will pick commits that need to be tested, check
them out, and then let you test them. (Unless, of course, you give git a
script that it can run to automatically test commits.) At this point you
can test and then:
1) Continue
eg bisect good # Mark the current commit as good, give me a new commit
OR
eg bisect bad # Mark the current commit as bad, give me a new commit
2) Skip this particular commit
eg bisect skip # Can't test the current version; give me a new commit
3) Abort
eg bisect reset
See 'man git-bisect' for more details."
}
sub refspecs {
return "
Before reading up on refspecs, be sure you understand all the following
help pages:
eg help merge
eg help pull
eg help push
eg help rebase
eg help remote
eg help topic storage
refspecs compress knowledge from pieces of all those things into a short
amount of space.
refspecs are command line parameters to eg push or eg pull, used at the end
of the command line. refspecs provide fine-grained control of pushing and
pulling changes in the following two areas:
Since branches, tags, and remote tracking branches are all implemented by
creating simple files consisting solely of a sha1sum, it is possible to
push to or pull from different reference names and different reference
types.
Pushing and pulling of (possibly remote tracking) branches are typically
accompanied by sanity checks to make sure the sha1sums on each end are
related (to make sure that updates don't throw away previous commits, for
example). In some cases it is desirable to ignore such checks, such as
when a branch has been rebased or commits have been amended.
The canonical format of a refspec is
[+]SRC:DEST
That is, an optional plus character followed by a source reference, then a
colon character, then the destination reference. There are a couple
special abbreviations, noted in the abbreviations section below. The
meaning and syntax of the parts of a refspec are discussed next.
General source and destination handling
Both the source and the destination reference are typically named by
their path specification under the .git directory. Examples:
refs/heads/bob # branch: bob
refs/tags/v2.0 # tag: v2.0
refs/remotes/jill/stable # remote-tracking branch: jill/stable
Leading directory paths can be omitted if no ambiguity would result.
The refspec specifies that the push or pull operation should take the
sha1sum from SRC in the source repository, and use it to fast-foward DEST
in the destination repository. The operation will fail if updating DEST
would not be a fast-foward, unless the optional plus in the refspec is
present.
Pull operations are somewhat unusual. For a pull, DEST is usually not
the current branch. In such cases, the current branch is also updated
after DEST is. The method of updating depends on whether --rebase was
specified, and whether the latest revision of the current branch is an
ancestor of the revision stored by DEST:
If --rebase is specified:
Rebase the current branch against DEST
If --rebase is not specified, current branch is an ancestor of DEST:
Fast-forward the current branch to DEST
If --rebase is not specified, current branch is not an ancestor of DEST:
Merge DEST into the current branch
Overriding push and pull sanity checks
For both push and pull operations, the operation will fail if updating
DEST to SRC is not a fast-forward. This tends to happen in a few
different circumstances:
For pushes:
* If someone else has pushed updates to the specified location
already -- in such cases one should resolve the problem by doing a
pull before attempting a push rather than overriding the safety
check.
* If one has rewritten history (e.g. using rebase, commit --amend,
reset followed by subsequent commits)
For pulls:
* If one is pulling to a branch instead of a remote tracking branch
-- in such a case, one should instead either specify a remote
tracking branch for DEST or specify an empty DEST rather than
overriding the safety check.
* If one has somehow recorded commits directly to a remote tracking
branch
* If history has been rewritten on the remote end (e.g. by using
rebase, commit --amend, reset followed by subsequent commits).
In all such cases, users can choose to throw away any existing unique
commits at the DEST end and make DEST record the same sha1sum as SRC, by
using a plus character at the beginning of the refspec.
Abbreviations of refspecs
Globbing syntax
For either pushes or pulls, one can use a globbing syntax, such as
refs/heads/*:refs/remotes/jim/*
or
refs/heads/*:refs/heads/*
in order to specify pulling or pushing multiple locations at once.
The following special abbreviations are allowed for both pushes and pulls:
tag TAG
This is equivalent to specifying refs/tags/TAG:refs/tags/TAG.
The following special abbreviations are allowed for pushes:
:REFERENCE
This specifies delete the reference at the remote end (think of it as
\"using nothing to update the remote reference\")
REFERENCE
This is the same as REFERENCE:REFERENCE
The following special abbreviations are allowed for pulls:
REFERENCE:
This is used to merge REFERENCE into the current branch directly
without storing the remote branch in some remote tracking branch.
REFERENCE
This is the same as REFERENCE: which is explained above.
";
}
sub remote_urls {
#
# NOTE: The help for remote_urls is basically lifted from the git manpages,
# which are licensed under GPLv2 (as is eg).
#
return "
Any of the following notations can be used to name a remote repository:
rsync://host.xz/path/to/repo.git/
http://host.xz/path/to/repo.git/
https://host.xz/path/to/repo.git/
git://host.xz/path/to/repo.git/
git://host.xz/~user/path/to/repo.git/
ssh://[user@]host.xz[:port]/path/to/repo.git/
ssh://[user@]host.xz/path/to/repo.git/
ssh://[user@]host.xz/~user/path/to/repo.git/
ssh://[user@]host.xz/~/path/to/repo.git
You can also use any of the following, which are identical to the last
three above, respectively
[user@]host.xz:/path/to/repo.git/
[user@]host.xz:~user/path/to/repo.git/
[user@]host.xz:path/to/repo.git
Finally, you can also use the following notation to name a not-so-remote
repository:
/path/to/repo.git/
file:///path/to/repo.git/
These last two are identical other than that the latter disables some local
optimizations (such as hardlinking copies of history when cloning, in order
to save disk space).
";
}
sub revisions {
#
# NOTE: The pictoral example of revision suffixes is taken from the
# git-rev-parse manpage, which is licensed under GPLv2 (as is eg).
#
return "
There are MANY different ways to refer to revisions (also referred to as
commits) of the repository. Most are only needed for fine-grained control
in very large projects; the basics should be sufficient for most.
Basics
The most common ways of referring to revisions (or commits), are:
- Branch or tag name (e.g. stable, v0.77, master, 2.28branch, version-1-0)
- Counting back from another revision (e.g. stable~1, stable~2, stable~3)
- Cryptographic checksum (e.g. dae86e1950b1277e545cee180551750029cfe735)
- Abbreviated checksum (e.g. dae86e)
The output of 'eg log' shows (up to) two names for each revision: its
cryptographic checksum and the count backward relative to the currently
active branch (if the revision being shown in eg log is not part of the
currently active branch then only the cryptographic checksum is shown).
One can always check the validity of a revision name and what revision
it refers to using 'eg log -1 REVISION' (the -1 to show only one revision).
Branches and Tags
Users can specify a tag name to refer to the revision marked by that tag.
Run 'eg tag' to get a list of existing tags.
Users can specify a branch name to refer to the most recent revision of
that branch. Use 'eg branch' to get a list of existing branches.
Cryptographic checksums
Each revision of a repository has an associated cryptographic checksum
(in particular, a sha1sum) identifying it. This cryptographic checksum
is a sequence of 40 letters and numbers from 0-9 and a-f. For example,
dae86e1950b1277e545cee180551750029cfe735
In addition to using these sha1sums to refer to revisions, one can also
use an abbreviation of a sha1sum so long as enough characters are used to
uniquely identify the revision (typically 6-8 characters are enough).
Special Names
There are a few special revision names.
Names that always exist:
HEAD - A reference to the most recent revision of the current branch
(thus HEAD refers to the same revision as using the branch
name). If there is no active branch, such as after running
'eg switch TAG', then HEAD refers to the revision switched to.
Note that the files in the working copy are always considered to
be a (possibly modifed) copy of the revision pointed to by HEAD.
Names that only exist in special cases:
ORIG_HEAD - Some operations (such as merge or reset) change which
revision the working copy is relative to. These will
record the old value of HEAD in ORIG_HEAD. This allows
one to undo such operations by running
eg reset --working-copy ORIG_HEAD
FETCH_HEAD - When downloading branches from other repositories (via
the fetch or pull commands), the tip of the last fetched
branch is stored in FETCH_HEAD.
MERGE_HEAD - If a merge operation results in conflicts, then the merge
will stop and wait for you to manually fix the conflicts.
In such a case, MERGE_HEAD will store the tip of the
branch(es) being merged into the current branch. (The
current branch can be accessed, as always, through HEAD.)
Suffixes for counting backwards
There are two suffixes for counting backwards from revisions to other
revisions: ~ and ^.
Adding ~N after a revision, with N a non-negative integer, means to count
backwards N commits before the specified revision. If any revision along
the path has more than one parent (i.e. if any revision is a merge
commit), then the first parent is always followed. Thus, if stable is a
branch, then
stable means the last revision on the stable branch
stable~1 means one revision before the last on the stable branch
stable~2 means two revisions before the last on the stable branch
stable~3 means three revisions before the last on the stable branch
In short, ~N goes back N generation of parents, always following the
first parent.
Adding ^N after a revision, with N a non-negative integer, means the Nth
parent of the specified revision. N can be omitted in which case it is
assumed to have the value 1. Thus, if stable is a branch, then
stable means the last revision on the stable branch
stable^1 means the first parent of the last revision on the stable branch
stable^2 means the second parent of the last revision on the stable branch
stable^3 means the third parent of the last revision on the stable branch
In short, ^N picks out one parent from the first generation of parents.
Revisions with suffixes can themselves have suffixes, thus
stable~5 = stable~3~2
Here is an illustration with an unusually high amount of merging. The
illustration has 10 revisions each tagged with a different letter of the
alphabet, with A referring to the most recent revision:
A
/ \\
/ \\
B C
/|\\ |
/ | \\ |
/ | \\ /
D E F
/ \\ / \\
G H I J
From the illustration, the following equalities hold:
A = = A^0
B = A^ = A^1 = A~1
C = A^2 = A^2
D = A^^ = A^1^1 = A~2
E = B^2 = A^^2
F = B^3 = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2 = B^^2 = A^^^2 = A~2^2
I = F^ = B^3^ = A^^3^
J = F^2 = B^3^2 = A^^3^2
Revisions from logged branch tip history
By default, all changes to each branch and to the special identifier HEAD
are recorded in something called a reflog (short for \"reference log\",
because calling it a \"branch log\" would not have made the glossary of
special terms long enough). Each entry of the reflog records the
previous revision recorded by the branch, the new revision the branch was
changed to, the command used to make the change (commit, merge, reset,
pull, checkout, etc.), and when the change was made. One can get an
overview of the changes made to a branch (including the special branch
'HEAD') by running
eg reflog show BRANCHNAME
One can make use of the reflog to refer to revisions that a branch used
to point to. The format for referring to revisions from the reflog are
BRANCH\@{HISTORY_REFERENCE}
Examples follow.
Revisions that the branch pointed to, in order
Assuming that ultra-bling is the name of a branch, the following can be
used to refer to revisions ultra-bling used to point to:
ultra-bling\@{0} is the same as ultra-bling
ultra-bling\@{1} is the revision pointed to before the last change
ultra-bling\@{2} is the revision ultra-bling pointed to two changes ago
ultra-bling\@{3} is the revision ultra-bling pointed to three changes ago
Note that any of these beyond the first could easily refer to commits
that are no longer part of the ultra-bling branch (due to using a
command like reset or commit --amend).
Revisions that the branch pointed to at a previous time
Assuing that fixes is the name of a branch, the following can be used to
refer to revisions that fixes used to point to:
fixes\@{yesterday} - revision fixes pointed to yesterday
fixes\@{1 day 3 hours ago} - revision fixes pointed to 1 day 3 hours ago
fixes\@{2008-02-29 12:34:00} - revision fixes had at 12:34 on Feb 29, 2008
Again, these could refer to revisions that are no longer part of the
fixes branch,
Using the branch log can be used to recover \"lost\" revisions that are
no longer part of (or have never been part of) any branch reported by 'eg
branch'.
Commit messages
One can also refer a revision using the beginning of the commit message
recorded in it. This is done using with the two-character prefix :/
followed by the beginning of the commit message. Note that quotation marks
are also often used to avoid having the shell split the commit message into
different arguments. Examples:
:/\"Fix the biggest bug blocking the 1.0 release\"
:/\"Make the translation from url\"
:/\"Add a README file\"
Note that if the commit message starts with an exclamation mark ('!'), then
you need to type two of them; for example example:
:/\"!!Commit messages starting with an exclamation mark are retarded\"
Other methods
There are even more methods of referring to revisions. Run \"man
git-rev-parse\", and look for the \"SPECIFYING REVISIONS\" section for
more details.
";
}
sub staging {
return "
Marking changes from certain files as ready for commit allows you to split
your changes into two distinct sets (those that are ready for commit, and
those that aren't). This includes support for limiting diffs to changes in
one of these two sets, and for committing just the changes that are ready.
It's a simple feature that comes in surprisingly handy:
* When doing conflict resolution from large merges, hunks of changes can
be categorized into known-to-be-good and still-needs-more-fixing
subsets.
* When reviewing a largish patch from someone else, hunks of changes can
be categorized into known-to-be-good and still-needs-review subsets.
* By staging your changes, you can go ahead and add temporary debugging
code and have less fear of forgetting to remove it before committing --
you will be warned about having both staged and unstaged changes at
commit time, and you will have an easy way to locate the temporary
code.
* It makes it easier to keep \"dirty\" changes in your working copy for a
long time without committing them.
Staging changes and working with staged changes
Mark all changes in foo.py and baz.c as ready to be committed
eg stage foo.py baz.c
Selectively stage part of the changes
eg stage -p
(You will be asked whether to stage each change, listed in diff format;
the main options to know are \"y\" for yes, \"n\" for no, and \"s\" for
splitting the selected change into smaller changes; see 'man git-add' for
more details).
Get all unstaged changes to bar.C and foo.pl
eg diff --unstaged foo.pl bar.C
Get all staged changes
eg diff --staged
Get all changes
eg diff
Revert the staged changes to bar.C, foo.pl and foo.py
eg unstage bar.C foo.pl foo.py
Commit just the staged changes
eg commit --staged
";
}
sub storage {
return "
Basics
Each revision is referred to by a cryptographic checksum (in particular,
a sha1sum) of its contents. Each revision also knows which revision(s)
it was derived from, known as the revision's parent(s).
Each branch records the cryptographic checksum of the most recent commit
for the branch. Since each commit records its parent(s), a branch
consists of its most recent commit plus all ancestors of that commit.
When a new commit is made on a branch, the branch just replaces the
cryptographic checksum of the old commit with the new one.
Remote tracking branches, if used (see 'eg help remote'), differ from
normal branches only in that they have a slash in their name. For
example, the remote tracking branch that tracks the contents of the
stable branch of the remote named bob would be called bob/stable. By
their nature, remote tracking branches only track the contents of a
branch of a remote repository; one does not switch to and commit to these
branches.
Tags simply record a single revision, much like branches, but tags are
not advanced when additional commits are made. Tags are not stored as
part of a branch, though by default tags that point to commits which are
downloaded (as part of merging changes from a branch) are themselves
downloaded as well.
Neither branches nor tags are revision controlled, though there is a log
of changes made to each branch (known as a reflog, short for \"reference
log\", because calling it a \"branch log\" wouldn't make the glossary of
special terms long enough).
Pictorial explanation
Using the letters A-P as shorthand for different revisions and their
cryptographic checksums (which we'll assume were created in the order
A...P for purposes of illustration), an example of the kind of structure
built up by having commits track their parents is:
N
|
M P
| |
L O
| \\ |
J K
| |
H I
| /
G
|
F
/ \\
C E
| |
B D
|
A
In this picture, F has two parents (C and E) and is thus a merge commit.
L is also a merge commit, having parents J and K. There are two branches
depicted here, which can be identified by N and P (due to the fact that
branches simply track their most recent commit). This history is
somewhat unusual in that there is no unique start of history; instead
there are two beginnings of history -- A and D. Such a history can be
created by pulling from, and merging with, a branch from another
repository that shares no common history. While unusual, it is fully
supported.
For further illustration, let's assume that the following branches exist:
stable: N
bling: P
Then the picture of each branch, side by side (using revision identifiers
explained in 'eg help topic revisions'), is:
stable
|
stable~1 bling
| |
stable~2 bling~1
| \\ |
stable~3 stable~2^2 bling~2
| | |
stable~4 stable~2^2~1 bling~3
| / /
stable~6 bling~4
| |
stable~7 bling~5
/ \\ / \\
stable~8 stable~7^2 bling~6 bling~5^2
| | | |
stable~9 stable~7^2~1 bling~7 bling~5^2~1
| |
stable~10 bling~8
Note that there are many commits which are part of both branches,
including two commits (I and K in the original picture) which were
probably created after these two branches separated. This is simply due
to recording both parents in merge commits.
Note that this tree-like or graph-like structure of branches is an
example of something that computer scientists call a Directed Acyclic
Graph (DAG); referring to it as such provides us the opportunity to
make the glossary of special terminology longer.
Files and directories in a git repository (stuff under .git)
You may find the following files and directories in your git repository.
This document will discuss the highlights; see the repository-layout.html
page distributed with git for more details.
COMMIT_EDITMSG
A leftover file from previous commits; this is the file that commit
messages are recorded to when when you do not specify a -m or -F option
to commit (thus causing an editor to be invoked).
config
A simple text file recording configuration options; see 'eg help config'.
description
A file that is only used by gitweb, currently. If you use gitweb, this
files provides a description of the project tracked in the repository.
HEAD
ORIG_HEAD
FETCH_HEAD
MERGE_HEAD
See the Special Names section of 'eg help topic revisions'; these files
record these special revisions.
git-daemon-export-ok
This file is only relevant if you are using git-daemon, a server to
provide access to your repositories via the git:// protocol.
git-daemon refuses to provide access to any repository that does not
have a git-daemon-export-ok file.
hooks
A directory containing customizations scripts used by various commands.
These scripts are only used if they are executable.
index
A binary file which records the staging area. See 'eg help topic
staging' for more information.
info
A directory with additional info about the repository
info/exclude
An additional place to specify ignored files. Users typically use
.gitignore files in the relevant directories to ignore files, but
ignored files can also be listed here.
info/ignored-unknown
A list of unknown files known to exist previously, used to determine
whether unknown files should cause commit (or push or publish) to
abort. See 'eg help commit' for more information; this list is
updated whenever the -b flag is passed to commit.
info/refs
This is a file created by 'eg update-server-info' and is needed for
repositories accessed over http.
logs
History of changes to references (i.e. to branches, tags, or
remote-tracking branches). The file logs/PATH/TO/FILE in the
repository records the changes to the reference PATH/TO/FILE in the
repository. See also the 'Revisions from logged branch tip history'
section of 'eg help topic revisions'.
objects
Storage of actual user data (files, directory trees, commit objects).
Storage is done according to sha1sum of each object (splitting sha1sums
into a combination of directory name and file name). There are also
packs, which compress many objects into one file for tighter storage
and reduced disk usage.
packed-refs
The combination of paths, filenames, and sha1sums from many different
refs -- one per line; see refs below.
refs
Storage of references (branches, heads, or remote tracking branches).
Each reference is a simple file consisting of a sha1sum (see 'eg help
topic storage' for more information). The path provides the type of
the reference, the file name provides the name for the reference, and
the sha1sum is the revision the reference refers to.
Branches are stored under refs/heads/*, tags under refs/tags/*, and
remote tracking branches under refs/remotes/REMOTENAME/*. Note that
some of these references may appear in packed-refs instead of having
a file somewhere under the refs directory.
";
}
sub help {
my $self = shift;
my $help_msg;
# Get the topic we want more info on (replace dashes, since they can't
# be in function names)
my $topic = shift @ARGV;
my $orig_topic = $topic;
$topic =~ s/-/_/g if $topic;
### FIXME: Add the following topics, plus maybe some others
# glossary <Not yet written; this is just a stub>
my $topics = "
middle-of-am How to resolve or abort an incomplete am (apply mail)
middle-of-bisect How to continue or abort a bisection
middle-of-merge How to resolve or abort an incomplete merge
middle-of-rebase How to resolve or abort an incomplete rebase
refspecs Advanced pushing and pulling: detailed control of storage
remote-urls Format for referring to remote (and not-so-remote) repositories
revisions Various methods for referring to revisions
staging Marking a subset of the local changes ready for committing
storage High level overview of how commits, tags, and branches are stored
";
if (defined $topic) {
die "No topic help for '$topic' exists. Try 'eg help topic'.\n"
if !$self->can($topic);
$help_msg = $self->$topic();
if ($topics =~ m#^(\Q$orig_topic\E.*)#m) {
$topic = $1;
}
} else {
$topic = "Topics";
$help_msg = $topics;
}
$ENV{"LESS"} = "FRSX" unless defined $ENV{"LESS"};
my $less = ($use_pager == 1) ? "less" :
($use_pager == 0) ? "cat" :
`$git_cmd config core.pager` || "less";
chomp($less);
open(OUTPUT, "| $less");
print OUTPUT "$topic\n";
print OUTPUT $help_msg;
close(OUTPUT);
exit 0;
}
###########################################################################
# info #
###########################################################################
package info;
@info::ISA = qw(subcommand);
INIT {
$command{info} = {
new_command => 1,
section => 'discovery',
extra => 1,
about => 'Show some basic information about the current repository'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_equivalent => '', git_repo_needed => 0, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg info [/PATH/TO/REPOSITORY]
Description:
Shows information about the specified repository, or the current
repository if none is specified.
Most of the output of eg info is self-explanatory, but some fields
benefit from extra explanation or pointers about where to find related
information. These fields are:
Total commits
The total number of commits (or revisions) found in the repository.
eg log can be used to view revision authors, dates, and commit log
messages.
Local repository
eg has a number of files and directories it uses to track your data,
including (by default) a copy of the entire history of the project.
These files and directories are all stored below a single directory,
referred to as the local repository. See 'eg help topic storage' for
more details.
Named remote repositories
To make it easier to track changes from multiple remote repositories,
eg provides the ability to provide nicknames for and work with
multiple branches from a remote repository and even working with
multiple remote repositories at once. See 'eg help remote' for more
details, though you will want to make sure you understand 'eg help
pull' and 'eg help push' first.
Current branch
All development is done on a branch, though smaller projects may only
use one branch per repository (thus making the repository effectively
serve as a branch). In contrast to cvs and svn which refer to
mainline development as \"HEAD\" and \"TRUNK\", respectively, eg
calls the mainline development a branch as well, with the default
name of \"master\"). See 'eg help branch' and 'eg help topic
storage' for more details.
Cryptographic checksum
Each revision has an associated cryptographic checksum of both its
contents and the revision(s) it was derived from, providing strong
data consistency checks and guarantees. These checksums are shown in
the output of eg log, and serve as a way to refer to revisions. See
also 'eg help topic storage' for more details.
Default pull/push configuration options:
The default repository to push to or pull from defaults to 'origin',
if the 'origin' remote has been set up (see 'eg help remote' for
setting up remote repository nicknames).
However, the default repository can be set on a per-branch basis as a
configuration option (see 'eg help config'). In fact, a number of
default pull/push actions can be set as per-branch configuration
options: default merge options to use on a given branch, default
branch to merge with from the remote repository, and whether to
rebase (rewrite local commits on top of new remote commits; see 'eg
help rebase') rather than merge (keep local commits as they are and
just make a merge commit combining local and remote changes; see 'eg
help merge').
";
$self->{'differences'} = '
eg info is unique to eg; git does not have a similar command. It
originally was intended just to do something nice if svn converts happen
to try this command, but I have found it to be a really nice way of
helping users get their bearings. It also provides some nice statistics
that git users may appreciate (particularly when it comes time to fill
out the Git User Survey).
';
return $self;
}
sub preprocess {
my $self = shift;
my $path = shift @ARGV;
die "Aborting: Too many arguments to eg info.\n" if @ARGV;
if ($path) {
die "$path does not look like a directory.\n" if ! -d $path;
my ($ret, $useless_output) =
ExecUtil::execute_captured("$git_cmd ls-remote $path", ignore_ret => 1);
if ($ret != 0) {
die "$path does not appear to be a git archive " .
"(maybe it has no commits yet?).\n";
}
chdir($path);
}
# Set git_dir
$self->{git_dir} = RepoUtil::git_dir();
die "Must be run inside a git repository!\n" if !defined $self->{git_dir};
}
sub run {
my $self=shift;
my $branch = RepoUtil::current_branch();
#
# Special case the situation of no commits being present
#
if (RepoUtil::initial_commit()) {
if ($debug < 2) {
print STDERR <<EOF;
Total commits: 0
Local repository: $self->{git_dir}
There are no commits in this repository. Please use eg stage to mark new
files as being ready to commit, and eg commit to commit them.
EOF
}
exit 1;
}
#
# Repository-global information
#
# total commits
my $total_commits = ExecUtil::output("$git_cmd rev-list --all | wc -l");
print "Total commits: $total_commits\n" if $debug < 2;
# local repo
print "Local repository: $self->{git_dir}\n" if $debug < 2;
# named remote repos
my %remotes;
my $longest = 0;
my @abbrev_remotes = split('\n', ExecUtil::output("$git_cmd remote"));
foreach $remote (@abbrev_remotes) {
chomp($remote);
my $url = RepoUtil::get_config("remote.$remote.url");
$remotes{$remote} = $url;
$longest = main::max($longest, length($remote));
}
if (scalar keys %remotes > 0 && $debug < 2) {
print "Named remote repositories: (name -> location)\n";
foreach my $remote (sort keys %remotes) {
printf " %${longest}s -> %s\n", $remote, $remotes{$remote};
}
}
#
# Stats for the current branch...
#
return if !defined($branch);
# File & directory stats only work if we're in the toplevel directory
my ($orig_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
chdir($top_dir);
# Name
print "Current branch: $branch\n" if $debug < 2;
# Sha1sum
my $current_commit = ExecUtil::output("$git_cmd show-ref -s -h | head -n 1");
print " Cryptographic checksum (sha1sum): $current_commit\n" if $debug < 2;
# Default pull/push options
my $default = "-None-";
my $print_config_options = 0;
my ($ret, $options) =
ExecUtil::execute_captured("$git_cmd config --get-regexp " .
"^branch\.$branch\.*", ignore_ret => 1);
chomp($options);
my @lines;
if ($ret == 0) {
@lines = split('\n', $options);
my $line_count = scalar(@lines);
$print_config_options = ($line_count > 0);
if ($options =~ /^branch\.$branch\.remote (.*)$/m) {
$default = $1;
$print_config_options = ($line_count > 1);
} else {
my @output = `$git_cmd config --get-regexp remote.origin.*`;
$default = "origin" if @output;
}
}
print " Default pull/push repository: $default\n" if $debug < 2;
if ($print_config_options && $debug < 2) {
print " Default pull/push options:\n";
foreach my $line (@lines) {
$line =~ s/\s+/ = /;
print " $line\n";
}
}
# No. contributors
my $contributors = ExecUtil::output("$git_cmd shortlog -s -n HEAD | wc -l");
print " Number of contributors: $contributors\n" if $debug < 2;
# No. files
my $num_files = ExecUtil::output("$git_cmd ls-tree -r HEAD | wc -l");
print " Number of files: $num_files\n" if $debug < 2;
# No. dirs
my $num_dirs = ExecUtil::output(
"$git_cmd ls-tree -r -t HEAD " .
" | grep -E '[0-9]+ tree'" .
" | wc -l");
print " Number of directories: $num_dirs\n" if $debug < 2;
# Some ugly, nasty code to get the biggest file. Seems to be the only
# method I could find that would work given the corner case filenames
# (spaces and unicode chars) in the git.git repo (Try eg info on repo
# from 'git clone git://git.kernel.org/pub/scm/git/git.git').
my @files = `$git_cmd ls-tree -r -l --full-name HEAD`;
my %biggest = (name => '', size => 0);
foreach my $line (@files) {
if ($line =~ m#^[0-9]+ [a-z]+ [0-9a-f]+[ ]*(\d+)[ \t]*(.*)$#) {
my ($size, $file) = ($1, $2);
if ($file =~ m#^\".*\"#) { $file = eval "$file" }; # Unicode fix
if ($size >= $biggest{size}) {
$biggest{name} = $file;
$biggest{size} = $size;
}
}
}
my $biggest_file = "$biggest{size} ($biggest{name})";
print " Biggest file size, in bytes: $biggest_file\n" if $debug < 2;
# No. commits
my $branch_depth = ExecUtil::output("$git_cmd rev-list HEAD | wc -l");
print " Commits: $branch_depth\n" if $debug < 2;
# Other possibilities:
# Disk space used by respository (du -hs .git, or packfile size?)
# Disk space used by working copy (???)
# Number of unpacked objects?
chdir($orig_dir);
# Well, if we got this far, it must have worked, so...
return 0;
}
###########################################################################
# init #
###########################################################################
package init;
@init::ISA = qw(subcommand);
INIT {
$command{init} = {
unmodified_behavior => 1,
section => 'creation',
about => 'Create a new repository'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 0, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg init [--shared]
Description:
Creates a new repository.
If you want to publish a copy of an existing repository so that others
can access it, use eg publish instead.
Note for cvs/svn users: With cvs or svn it is common to create an empty
repository on \"the server\", then check it out locally and start adding
files and committing. With eg, it is more natural to create a repository
on your local machine and start creating and adding files, then later
(possibly as soon as one commit later) publishing your work to \"the
server\". git (and thus eg) does not currently allow cloning empty
repositories, so for now you must change habits.
Examples:
Create a new blank repository, then use it by creating and adding a file
to it:
\$ mkdir project
\$ cd project
\$ eg init
Create and edit a file called foo.py
\$ eg stage foo.py
\$ eg commit
Create a repository to track further changes to an existing project. Then
start using it right away
\$ cd cool-program
\$ eg init
\$ eg stage . # Recursively adds all files
\$ eg commit -m \"Initial import of all files\"
Make more changes to fix a bug or add a new feature or...
\$ eg commit
(Advanced) Create a new blank repository meant to be used in a
centralized fashion, i.e. a repository for many users to commit to.
\$ mkdir new-project
\$ cd new-project
\$ eg init --shared
Check repository ownership and user groups to ensure they are right
Options:
--shared
Set up a repository that will shared amongst several users; note that
you are responsible for creating a common group for developers so that
they can all write to the repository. Ask your sysadmin or see the
groupadd(8), usermod(8), chgrp(1), and chmod(1) manpages.
";
return $self;
}
###########################################################################
# log #
###########################################################################
package log;
@log::ISA = qw(subcommand);
INIT {
$command{log} = {
section => 'discovery',
about => 'Show history of recorded changes'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg log
Description:
Shows a history of recorded changes. Displays commit identifiers,
the authors of the changes, and commit messages.
";
$self->{'differences'} = '
eg log output differs from git log output by showing simpler revision
identifiers that will be easier for new users to understand and use.
In detail:
eg log
is essentially the same as
git log | git name-rev --stdin --refs=$(git symbolic-ref HEAD) | less
However, it implements the name-rev behavior internally to provide
incremental history processing (which avoids slow upfront full-history
analyses) in common cases.
';
return $self;
}
sub _get_values {
my ($names, $sha1sum) = @_;
my ($name, $distance);
if (defined($names->{$sha1sum})) {
($name, $distance) = @{$names->{$sha1sum}};
}
return ($name, $distance);
}
sub _path_count {
my ($name) = @_;
my @matches = ($name =~ m/[~^]/g);
return scalar @matches;
}
sub _get_revision_name {
my ($sha1sum, $filehandle, $names) = @_;
my ($name, $distance);
# If we've already determined teh name of this sha1sum before, just return it
($name, $distance) = _get_values($names, $sha1sum);
return $name if defined $name;
# Loop over rev-list output, naming the parents of each commit as we walk
# backward in history (breaking whenever if we hit our sha1sum)
while (<$filehandle>) {
# Each line of the rev-list output is of form
# sha1sum-of-commit sha1sum-of-parent1 sha1sum-of-parent2...
my ($child, $parent, @merge_parents) = split;
next if !$parent;
# Determine the name of the current commit, and its distance from the head
# of the current branch
my ($cur_name, $distance) = _get_values($names, $child);
die "Yikes! Your history is b0rken!\n" if (!$cur_name);
# Determine any name we previously determined for $parent, the name we
# would give it relative to $child, and determine which should "win"
my ($orig_parent_name, $orig_parent_distance) = _get_values($names, $parent);
if ($cur_name =~ /^(.*)~(\d+)$/) {
my $count = $2 + 1;
$parent_name = "$1~$count";
} else {
$parent_name = "$cur_name~1";
}
$parent_distance = $distance + 1;
if (!$orig_parent_name ||
_path_count($orig_parent_name) > _path_count($parent_name)) {
$names->{$parent} = [$parent_name, $parent_distance];
}
# Do the same for other parents, though their naming scheme is slightly
# different
my $count=2;
foreach my $merge_parent (@merge_parents) {
($orig_parent_name, $orig_parent_distance) =
_get_values($names, $merge_parent);
if (!$orig_parent_name ||
_path_count($orig_parent_name) > _path_count("$cur_name^$count")) {
$names->{$merge_parent} = ["$cur_name^$count", $parent_distance];
}
$count++;
}
# Check if we found the needed sha1sum, and exit early if so
push(@merge_parents, $parent);
last if (grep {$_ eq $sha1sum} @merge_parents);
}
# Check if we found the needed sha1sum; if so, return it
($name, $distance) = _get_values($names, $sha1sum);
return $name if $name;
# We didn't find the wanted sha1sum; it has no name relative to the current
# branch using tildes and hats.
return "";
}
sub run {
my $self = shift;
@ARGV = Util::quote_args(@ARGV);
my $branch = RepoUtil::current_branch();
# Check whether to warn if there are no commits. We don't want to parse
# all the arguments to determine if there is a valid revision listed on the
# command line (or, failing that, whether HEAD reference a valid revision);
# instead, we just check for the simple case of no branches existing yet.
if (!`$git_cmd branch -a`) {
die "Error: No recorded commits to show yet.\n";
}
# We can just run plain git log if there's not current branch
if (!$branch || !RepoUtil::valid_ref($branch)) {
return ExecUtil::execute("$git_cmd log @ARGV", ignore_ret => 1);
}
my ($ret, $revision) =
ExecUtil::execute_captured("$git_cmd rev-parse refs/heads/$branch");
exit $ret if $ret;
chomp($revision);
# Show the user the essential equivalent to what we manually do
if ($debug) {
print " >>Running: $git_cmd log @ARGV | \\\n" .
" $git_cmd name-rev --stdin " .
"--refs=refs/heads/$branch | \\\n" .
" less\n";
return if $debug == 2;
}
# Setup name determination via output from git rev-list
my %names;
open(REV_LIST_INPUT, "$git_cmd rev-list --parents $branch | ");
$names{$revision} = [$branch, 0];
# Loop over the output of git log, printing/modifying as we go
my $use_colors = -t STDOUT ? "GIT_PAGER_IN_USE=1" : "";
open(INPUT, "$use_colors $git_cmd log @ARGV | ");
$ENV{"LESS"} = "FRSX" unless defined $ENV{"LESS"};
my $less = ($use_pager == 1) ? "less" :
($use_pager == 0) ? "cat" :
`$git_cmd config core.pager` || "less";
chomp($less);
my $pid = open(OUTPUT, "| $less");
# Make sure that we don't leave the terminal in a weird state if the user
# hits Ctrl-C during eg log
local $SIG{INT} =
sub { kill SIGKILL, $pid; close(INPUT); close(OUTPUT); close(REV_LIST_INPUT);
exit(0); };
#open(OUTPUT, ">&STDOUT");
while (<INPUT>) {
# If it's a commit line, determine the name of the commit and print it too
# ANSI color escape sequences make this regex kind of ugly...
if (/^((?:\e\[.*?m)?commit ([0-9a-f]{40}))((?:\e\[m)?)$/) {
my $name = _get_revision_name($2, REV_LIST_INPUT, \%names);
print OUTPUT "$1 ($name)$3\n" if $name;
print OUTPUT "$1$3\n" if !$name;
} else {
print OUTPUT;
}
}
my ($ret1, $ret2, $ret3);
close(INPUT); $ret1 = $?;
close(OUTPUT); $ret2 = $?;
# Make sure we close the pipe from rev-list too; We use "$? && $!"
# instead of "$?" because we don't care about the return value of the
# rev-list program -- which we prematurely close -- just whether the close
# succeeded. We can't just use "$!" because if --pretty="format:%s" is
# passed to eg log, then $! will be "Bad file descriptor" which translates
# to a nonzero exit status.
# (This is my best guess at what to do given the random failures from
# t1411-reflog-show.sh, and reading 'man perlfunc' under 'close'; it seems
# to work.)
close(REV_LIST_INPUT); $ret3 = $? && $!;
return $ret1 || $ret2 || $ret3;
}
###########################################################################
# merge #
###########################################################################
package merge;
@merge::ISA = qw(subcommand);
INIT {
$command{merge} = {
unmodified_behavior => 1,
section => 'projects',
about => 'Join two or more development histories (branches) together'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg merge [-m MESSAGE] BRANCH...
Description:
Merges another branch (or more than one branch) into the current branch.
You may want to skip to the examples; the remainder of this description
section just has boring details about how merges work.
There are three different ways to handle merges depending on whether the
current branch or the specified merge branches have commits not found in
the other. These cases are:
1) The current branch contains all commits in the specified branch(es).
In this case, there is nothing to do.
2) Both the current branch and the specified merge branch(es) contain
commits not found in the other:
In this case, a new commit will be created which (a) includes
changes from both the current branch and the merge branch(es) and
(b) records the parents of the new commit as the last revision of
the current branch and the last revision(s) of the merge
branch(es).
3) The specified merge branch has all the commits found in the current
branch.
In this case, a new commit is not needed to merge the branches
together. Instead, the current branch simply changes the record
of its last revision to that of the specified merge branch. This
is known as a fast-forward update.
See 'eg help topic storage' for more information.
Examples:
Merge all changes from the stable branch that are not already in the
current branch, into the current branch.
\$ eg merge stable
Merge all changes from the refactor branch into the current branch (i.e.
same as the previous example but merging in a different branch)
\$ eg merge refactor
Options:
-m MESSAGE
Use MESSAGE as the commit message for the created merge commit, if
a merge commit is needed.
";
return $self;
}
###########################################################################
# publish #
###########################################################################
package publish;
@publish::ISA = qw(subcommand);
INIT {
$command{publish} = {
extra => 1,
new_command => 1,
section => 'collaboration',
about => 'Publish a copy of the current repository on a remote machine'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
git_equivalent => '',
initial_commit_error_msg => "Error: No recorded commits to publish.",
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg publish [--bypass-modification-check] REMOTE_DIRECTORY
Description:
Publishes a copy of the current repository on a remote machine. Note
that local changes will be ignored; only committed changes will be
published. You must have ssh access to the remote machine and must have
both rsync and ssh installed on your local machine (every modern distro
or OS installs both by default).
The remote directory should be specified using rsync syntax, even if the
remote repository will be accessed by some other protocol. Typical rsync
syntax for a (usually remote) directory is
[[USER@]MACHINE:]PATH
If PATH is not absolute and MACHINE is specified, it is taken as relative
to the user's home directory on MACHINE. See the examples below for more
detail, or the rsync(1) manpage. If any files or directories exist below
the specified remote directory, they will be removed or replaced.
Note that if git is not installed on the remote machine, you will be
unable to push updates to the remote repository (however, you can
republish over the top of the previous copy).
Examples:
Publish a copy of the current repository on the machine myserver.com in
the directory /var/scratch/git-stuff/my-repo.git. Then immediately
make a clone of the remote repository
\$ eg publish myserver.com:/var/scratch/git-stuff/my-repo.git
\$ cd
\$ eg clone myserver.com:/var/scratch/git-stuff/my-repo.git
Publish a copy of the current repository on the machine www.gnome.org, in
the public_html/myproj subdirectory of the home directory of the remote
user fake, then immediately clone it again into a separate directory
named another-myproj.
\$ eg publish fake\@www.gnome.org:public_html/myproj
\$ cd
\$ eg clone http://www.gnome.org/~fake/myproj another-myproj
Options
--bypass-modification-check, -b
To prevent you from publishing an incomplete set of changes, publish
typically checks whether you have new unknown files or modified files
present and aborts if so. You can bypass these checks with this
option.
";
$self->{'differences'} = '
eg publish is unique to eg; git makes publishing repositories annoyingly
painful. The steps that eg publish performs are (assuming one is in the
toplevel directory and that GIT_DIR=.git):
touch .git/git-daemon-export-ok
git gc
cd .git
git --bare update-server-info
mv .git/hooks/post-update.sample .git/hooks/post-update
chmod u+x hooks/post-update
cd ..
rsync -e ssh -az --delete .git REMOTE_DIRECTORY
Since this does make some minor changes to the local repository that are
unnecessary after the rsync command has completed, I might add some code
to try to clean the .git directory back up. I doubt any of it will hurt
if I do not get around to it, though.
eg publish also will setup the published repository as the new origin (if
a remote named origin does not already exist), so that future pushes and
pulls use the published repository.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
my $bypass_modification_check = 0;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"bypass-modification-check|b" => \$bypass_modification_check,
);
die "Invalid/insufficient args to eg publish: @ARGV\n" if @ARGV != 1;
$self->{remote_dir} = shift @ARGV;
if (!$bypass_modification_check) {
my $status = RepoUtil::commit_push_checks($package_name,
{unknown => 1, changes => 1});
} else {
# Record the set of unknown files we ignored with -b, so the -b flag
# isn't needed next time.
RepoUtil::record_ignored_unknowns();
}
}
sub run {
my $self = shift;
my $orig_dir = main::getcwd();
chdir($self->{git_dir});
print " >>Running: 'cd $self->{git_dir}'<<\n" if $debug;
#
# Warn the user if they have files that may have too restrictive permissions
#
my @non_readable_files = `find . ! -perm -004`;
if (scalar @non_readable_files > 0) {
print STDERR <<EOF;
WARNING: Some files under $self->{git_dir} are not world readable (see the
chmod(1) manpage). These permissions will be preserved in the published
copy, so you will need to manually change the permissions of the published
repository if you want them to be world readable (alternatively, you could
modify the permissions of files under $self->{git_dir} and re-run eg publish.)
EOF
}
#
# Setup the repository for rsyncing
#
ExecUtil::execute("touch git-daemon-export-ok");
print "Optimizing local repository and compressing it...\n" if $debug < 2;
ExecUtil::execute("$git_cmd gc");
ExecUtil::execute("$git_cmd --bare update-server-info");
if (-f "hooks/post-update.sample") {
ExecUtil::execute("mv hooks/post-update.sample hooks/post-update");
}
ExecUtil::execute("chmod u+x hooks/post-update");
my $is_bare = ExecUtil::output("$git_cmd config --get core.bare");
ExecUtil::execute("$git_cmd config core.bare true");
#
# rsync .git to the publish location
#
print "Copy local repository to remote location...\n" if $debug < 2;
my $ret = ExecUtil::execute(
"rsync -e ssh -az --delete --exclude=refs/remotes " .
"--exclude=COMMIT_EDITMSG --exclude=index --exclude=logs " .
"--exclude=info/ignored-unknown " .
"--exclude=ORIG_HEAD --exclude=FETCH_HEAD --exclude=MERGE_HEAD " .
"./ \"$self->{remote_dir}\"");
exit $ret if $ret;
#
# Undo any temporary changes we did for publishing
#
ExecUtil::execute("$git_cmd config core.bare $is_bare");
# FIXME: I should clean up git-daemon-export-ok, hooks/post-update, and
# the files that 'man git-update-server-info' says it creates.
#
# Set up the published repository as the default ("origin"), if origin
# is not already setup. Otherwise, tell the user how to simplify
# future pushes/pulls.
#
my @output = `$git_cmd config --get-regexp remote.origin.*`;
if (@output) {
print <<EOF;
NOTE: Not setting up $self->{remote_dir} as your default push/pull location,
since you already have one. To set up easy pushes and pulls to this location,
run
eg remote add NICKNAME $self->{remote_dir}
and then do future pushes and pulls with
eg push NICKNAME
eg pull NICKNAME
EOF
} else {
ExecUtil::execute("$git_cmd remote add origin $self->{remote_dir}");
#
# Set up the configuration variables branch.BRANCH.(remote|merge)
# for each BRANCH (used later as default push/pull locations)
#
my @branches = `$git_cmd branch`;
print " >>Running: '$git_cmd branch'<<\n" if $debug;
foreach my $branch (@branches) {
chomp($branch);
$branch =~ s#..##;
next if $branch eq "HEAD";
RepoUtil::set_config("branch.$branch.remote", "origin");
RepoUtil::set_config("branch.$branch.merge", "refs/heads/$branch");
}
}
chdir($orig_dir);
return 0;
}
###########################################################################
# pull #
###########################################################################
package pull;
@pull::ISA = qw(subcommand);
INIT {
$command{pull} = {
section => 'collaboration',
about => 'Get updates from another repository and merge them'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg pull [--branch BRANCH] [--no-tags] [--all-tags] [--tag TAG]
[--no-commit] [--rebase] REPOSITORY
Description:
Pull changes from another repository and merge them into the local
repository. If there are no conflicts, the result will be committed.
See 'eg help topic remote-urls' for valid syntax for remote repositories.
If you frequently pull from the same repository, you may want to set up a
nickname for it (see 'eg help remote'), so that you can specify the
nickname instead of the full repository URL every time.
By default, tags in the remote repository associated with commits that
are pulled, will themselves be pulled. One can specify to pull
additional or fewer tags with the --all-tags, --no-tags, or --tag TAG
options.
If there is more than one branch (on either end):
If the local repository has more than one branch, the changes are
always merged into the active branch (use 'eg info' or 'eg branch' to
determine the active branch).
If you do not specify which remote branch to pull, and you have not
previously pulled a remote branch from the given repository, then eg
will abort and ask you to specify a remote branch (giving you a list to
choose from).
Note for users of named remote repositories and remote tracking branches:
If you set up named remote repositories (using 'eg remote'), you can
make 'eg pull' obtain changes from several branches at once. In such a
case, eg will take the changes and record them in special local
branches known as \"remote tracking branches\", a step which involves
no merging. Most of these branches will not be handled further after
this step. eg will then take changes from just the branch(es)
specified (with the --branch option, or with the
branch.CURRENTBRANCH.merge configuration variable, or by the last
branch(es) merged), and merge it/them into the active branch.
The advantage of pulling changes from branches that you do not
immediately merge with is that you can then later inspect, review, or
merge with such changes (using 'eg merge') even if not connected to the
network. Naming the remote repositories also allows you to use the
shorter name instead of the full location of the repository. (eg
remote also provides the ability to update from groups of remote
repositories simultaneously.) See 'eg help remote' and 'eg help topic
storage' for more information about named remote repositories and
remote tracking branches.
Examples:
Pull changes from myserver.com:git-stuff/my-repo.git
\$ eg pull myserver.com:git-stuff/my-repo.git
Pull changes from the stable branch of git://git.foo.org/whizbang into the
active local branch
\$ eg pull --branch stable git://git.foo.org/whizbang
Pull changes from the debug branch in the remote repository nicknamed
'carl' (see 'eg help remote' for more information about nicknames for
remote repositories)
\$ eg pull --branch debug carl
Pull changes from a remote repository that has multiple branches
Hmm, we don't know which branches the remote repository has. Just
try it.
\$ eg pull ssh://machine.fake.gov/~user/hack.git
That gave us an error telling us it didn't know which branch to pull
from, but it told us that there were 3 branches: 'master', 'stable',
and 'nasty-hack'. Let's get changes from the nasty-hack branch!
\$ eg pull --branch nasty-hack ssh://machine.fake.gov/~user/hack.git
Options
--branch BRANCH
Merge the changes from the remote branch BRANCH. May be used multiple
times to merge changes from multiple remote branches at once.
--no-tags
Do not download any tags from the remote repository
--all-tags
Download all tags from the remote repository.
--tag TAG
Download TAG from the remote repository
--no-commit
Perform the merge but do not commit even if the merge is clean.
--rebase
Instead of a merge, perform a rebase; in other words rewrite commit
history so that your recent local commits become commits on top of the
changes downloaded from the remote repository.
NOTE: This is a potentially _dangerous_ operation. Rewriting history
that has been pushed or pulled into another repository can break
subsequent pushes and pulls with those repositories. (Such breaks can
be fixed, at the cost of having to modify the commit history of each
affected repository.) Do not use this option without thoroughly
understanding 'eg help rebase'.
";
$self->{'differences'} = "
eg pull and git pull are nearly identical. eg provides a slightly more
meaningful name for --tags (\"--all-tags\"), introduces a new option
named --branch, and tries to assist the user when no branch to
merge/rebase is specified on the command line or in the config.
The new --branch option (1) avoids the need to explain refspecs too early
to users, (2) makes command line examples more self-documenting. eg
still accepts refspecs at the end of the commandline the same as git
pull, however their explanation is deferred to 'eg help topic refspecs'.
When no branch to merge/rebase is specified, eg pull will provide a list
of known branches at the remote end. In the special case that the remote
has exactly one branch, eg will use that branch for merging/rebasing
rather than erroring out.
";
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
$self->{args} = [];
my $record_arg =
sub { my $prefix = "";
$prefix = "no-" if defined $_[1] && $_[1] == 0;
push(@{$self->{args}}, "--$prefix$_[0]");
};
my $record_args = sub { $_[0] = "--$_[0]"; push(@{$self->{args}}, @_); };
my ($no_tags, $all_tags) = (0, 0);
my @branches;
my @tags;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"--branch=s" => sub { push(@branches, $_[1]) },
"--tag=s" => sub { push(@tags, $_[1]) },
"--all-tags" => \$all_tags,
"--no-tags" => \$no_tags,
"commit!" => sub { &$record_arg(@_) },
"summary!" => sub { &$record_arg(@_) },
"no-stat|n" => sub { &$record_arg(@_) },
"squash!" => sub { &$record_arg(@_) },
"ff!" => sub { &$record_arg(@_) },
"strategy|s=s" => sub { &$record_args(@_) },
"rebase!" => sub { &$record_arg(@_) },
"quiet|q" => sub { &$record_arg(@_) },
"verbose|v" => sub { &$record_arg(@_) },
"append|a" => sub { &$record_arg(@_) },
"upload-pack=s" => sub { &$record_args(@_) },
"force|f" => sub { &$record_arg(@_) },
"tags" => \$all_tags,
"keep|k" => sub { &$record_arg(@_) },
"update-head-ok|u" => sub { &$record_arg(@_) },
"--depth=i" => sub { &$record_args(@_) },
);
die "Cannot specify both --all-tags and --no-tags!\n"
if $all_tags && $no_tags;
die "Cannot specify request tags along with --all-tags or --no-tags!\n"
if @tags && ($all_tags || $no_tags);
my $repository = shift @ARGV;
my @git_refspecs = @ARGV;
# Record the tags or no-tags arguments
push(@{$self->{args}}, "--tags") if $all_tags;
push(@{$self->{args}}, "--no-tags") if $no_tags;
#
# Get the repository to pull from
#
if ($repository) {
push(@{$self->{args}}, $repository);
} elsif (!$repository && @branches) {
$repository = RepoUtil::get_default_push_pull_repository();
push(@{$self->{args}}, $repository);
} else {
# Just drop through to the git pull defaults.
}
#
# Get the branch(es) to pull from
#
push(@branches, @git_refspecs);
if (!@branches && !@tags) {
my $branch = RepoUtil::current_branch();
if ($branch) {
my ($merge_branch, $url);
$url = RepoUtil::get_config("branch.$branch.remote");
if ($url && (!$repository || $url eq $repository)) {
$merge_branch = RepoUtil::get_config("branch.$branch.merge");
}
if (!$merge_branch && ($repository || $url)) {
my $only_branch = RepoUtil::get_only_branch($repository || $url);
push(@{$self->{args}}, $url) if !$repository;
push(@branches, $only_branch);
}
}
}
foreach my $branch (@branches) {
push(@{$self->{args}}, $branch);
}
foreach my $tag (@tags) {
push(@{$self->{args}}, ("tag", $tag));
}
}
sub run {
my $self = shift;
@args = Util::quote_args(@{$self->{args}});
return ExecUtil::execute("$git_cmd pull @args", ignore_ret => 1);
}
###########################################################################
# push #
###########################################################################
package push;
@push::ISA = qw(subcommand);
INIT {
$command{push} = {
section => 'collaboration',
about => 'Push local commits to a published repository'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
initial_commit_error_msg => "Error: No recorded commits to push.",
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg push [--bypass-modification-check] [--branch BRANCH] [--tag TAG]
[--all-branches] [--all-tags] [--mirror] REPOSITORY
Description:
Push committed changes in the current repository to a published remote
repository. The push can fail if the remote repository has commits not
in the current repository; this can be fixed by pulling and merging
changes from the remote repository (use eg pull for this) and then
repeating the push. Note that for getting changes directly to a fellow
developer's clone, you should have them use 'eg pull' rather than trying
to use 'eg push' on your end.
Branches and tags are typically considered private; thus only the current
branch will be involved by default (no tags will be sent). The
--all-branches, --matching-branches, --all-tags, and --mirror options
exist to extend the list of changes included. The --branch and --tag
options can be used to specifically send different changes.
See 'eg help topic remote-urls' for valid syntax for remote repositories.
If you frequently push to the same repository, you may want to set up a
nickname for it (see 'eg help remote'), so that you can specify the
nickname instead of the full repository URL every time.
Examples:
Push commits in the current branch
\$ eg push myserver.com:git-stuff/my-repo.git
Push commits in all branches that already exist both locally and remotely
\$ eg push --matching-branches ssh://web.site/path/to/project.git
Push commits in all branches, including branches that do no already exist
remotely, and all tags, to the remote nicknamed 'alice'
\$ eg push --all-branches --all-tags alice
Push all local branches and tags and delete anything on the remote end
that is not in the current repository
\$ eg push --mirror ssh://jim\@host.xz:22/~jim/project/published
Create a two new tags locally, then push both
\$ eg tag MY_PROJECT_1_0
\$ eg tag USELESS_ALIAS_FOR_1_0
\$ eg push --tag MY_PROJECT_1_0 --tag USELESS_ALIAS_FOR_1_0
Push the changes in just the stable branch
\$ eg push --branch stable
Options
--bypass-modification-check, -b
To prevent you from pushing an incomplete set of changes, push
typically checks whether you have new unknown files or modified files
present and aborts if so. You can bypass these checks with this
option.
--branch BRANCH
Push commits in the specified branch. May be reused multiple times to
push commits in multiple branches.
As an advanced option, one can use the syntax LOCAL:REMOTE for the
branch. For example, \"--branch my_bugfix:stable\" would mean to use
the my_bugfix branch of the current repository to update the stable
branch of the remote repository.
--tag TAG
Push the specified tag to the remote repository.
--all-branches
Push commits from all branches, including branches that do not yet exist
in the remote repository
--matching-branches
Push commits from all branches that exist locally and remotely. Note that
this option is ignored if specific branches or tags are specified, or the
--all-branches or --all-tags options.
--all-tags
Push all tags to the remote repository.
--mirror
Make the remote repository a mirror of the local one. This turns on
both --all-branches and --all-tags, but it also means that tags and
branches that do not exist in the local repository will be deleted from
the remote repository.
";
$self->{'differences'} = "
eg push tries to simplify git, but is essentially the same other than (1)
defaulting to pushing only the current branch instead of matching
branches, and (2) trying to avoid pushing into a bare repository. There
are two other minor changes, namely providing extra --tag and --branch
flags to make command lines more self-documenting and to avoid
introducing refspecs (a very confusing topic for new users) too early.
However, refspecs still work with eg push, and users can learn about them
by running 'eg help topic refspecs'.
Pushing just the current branch to its tracking branch is a change mostly
made with the idea that new git users coming from the cvs/svn world will
probably be using central repositories that many developers push to.
However, given the recent push.default config option introduced in
git-1.6.3 (which can be set to 'tracking' to get the eg behavior or
'matching' to get the old git default behavior), this change in eg may be
obsolete and reverted in the future.
The method eg push uses to avoid pushing into a non-bare repository is a
push-side check rather than a receive-side check. In cases it can detect
that the remote repository is bare (pushing to a directory on a locally
mounted filesystem or pushing over ssh), it will disable such pushes
unless the user specifies both source and destination references
explicitly (which can be done by running, for example, 'eg push origin
master:master'). However, given the new receive-side check introduced in
git-1.6.x to avoid pushing into bare repositories unless they have
configured to allow it, this change in eg may be obsolete and reverted in
the future.
";
return $self;
}
sub _get_push_repository {
my ($repository) = @_;
if (defined $repository) {
return RepoUtil::get_config("remote.$repository.url") || $repository;
} else {
return RepoUtil::get_config("remote.origin.url")
}
}
# _check_if_bare: Return whether the given repository is bare. Returns
# undef the repository doesn't specify a valid repository or the repository
# is not of a type where we can determine bare-ness. Otherwise returns
# either the string "true" or "false".
sub _check_if_bare {
my $repository = shift;
# Don't know how to check rsync, http, https, or git repositories to see
# if they are bare.
return undef if $repository =~ m#^(rsync|http|https|git)://#;
#
# Check local directories
#
if ($repository =~ m#^file://(.*)#) {
$repository = $1;
}
if (-d $repository) {
my $orig_dir = main::getcwd();
chdir($repository);
my ($ret, $output) =
ExecUtil::execute_captured("$git_cmd rev-parse --is-bare-repository",
ignore_ret => 1);
chdir($orig_dir);
return undef if $ret != 0;
chomp($output);
return $output;
}
#
# Check ssh systems
#
my ($machine, $path);
if ($repository =~ m#^ssh://((?:.*?@)?)([^/:]*)(?::[0-9]+)?(.*)$#) {
$user = $1;
$machine = $2;
$path = $3;
$path =~ s#^/~#~#; # Change leading /~ into plain ~
} elsif ($repository =~ m#^((?:.*?@)?)([^:]*):(.*)$#) {
$user = $1;
$machine = $2;
$path = $3;
}
return undef if !defined $machine || !defined $path;
my ($ret, $output) =
ExecUtil::execute_captured(
"ssh $user$machine 'cd $path && $git_cmd rev-parse --is-bare-repository'",
ignore_ret => 1);
return undef if $ret != 0;
chomp($output);
return $output;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
$self->{args} = [];
my $record_arg = sub { push(@{$self->{args}}, "--$_[0]"); };
my $record_args = sub { $_[0] = "--$_[0]"; push(@{$self->{args}}, @_); };
my ($all_branches, $matching_branches, $all_tags, $mirror) = (0, 0, 0, 0);
my ($thin, $repo) = (0, 0);
my @branches;
my @tags;
my $bypass_modification_check = 0;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"--branch=s" => sub { push(@branches, $_[1]) },
"--tag=s" => sub { push(@tags, $_[1]) },
"--all-branches|all" => \$all_branches,
"--matching-branches" => \$matching_branches,
"--all-tags" => \$all_tags,
"--mirror" => \$mirror,
"--dry-run" => sub { &$record_arg(@_) },
"--receive-pack=s" => sub { &$record_args(@_) },
"force|f" => sub { &$record_arg(@_) },
"repo=s" => \$repo,
"thin" => sub { &$record_arg(@_) },
"no-thin" => sub { &$record_arg(@_) },
"verbose|v" => sub { &$record_arg(@_) },
"bypass-modification-check|b" => \$bypass_modification_check,
);
die "Cannot specify individual branches and request all branches too!\n"
if @branches && ($all_branches || $mirror);
die "Cannot specify individual tags and request all tags too!\n"
if @tags && ($all_tags || $mirror);
my $repository = shift @ARGV;
my @git_refspecs = @ARGV;
if (!$bypass_modification_check) {
my $status = RepoUtil::commit_push_checks($package_name,
{unknown => 1, changes => 1});
} else {
# Record the set of unknown files we ignored with -b, so the -b flag
# isn't needed next time.
RepoUtil::record_ignored_unknowns();
}
push(@{$self->{args}}, "--all") if $all_branches;
push(@{$self->{args}}, "--tags") if $all_tags;
push(@{$self->{args}}, "--mirror") if $mirror;
my $default_specified = 0;
$default_specified = 1 if $all_branches;
$default_specified = 1 if $matching_branches;
$default_specified = 1 if $all_tags;
$default_specified = 1 if $mirror;
#
# Get the repository to push to
#
if (defined $repository && $repository =~ m#^-#) {
die "Invalid repository to push to: $repository\n";
}
my $remote;
if ($repository) {
push(@{$self->{args}}, $repository);
} elsif (!$repository && (@branches || @tags || !$default_specified)) {
$repository = RepoUtil::get_default_push_pull_repository();
push(@{$self->{args}}, $repository);
$remote = $repository;
} else {
# Just drop through to the git pull defaults.
}
#
# Prevent pushing to a non-bare repository (on local filesystem or over
# ssh; I don't know how to detect other cases)...unless user explicitly
# specifies both source and destination references explicitly
#
$repository = _get_push_repository($repository);
my $push_to_non_bare_repo;
if ($repository) {
# If the user uses a refspec including a colon character, assume
# they know what they are doing and skip the non-bare check
if (! grep {$_ =~ /:/} @git_refspecs) {
# Check if we have already determined this repository to be bare
my $is_bare;
$is_bare = RepoUtil::get_config("remote.$remote.bare") if $remote;
if (defined $is_bare) {
$push_to_non_bare_repo = ($is_bare eq "false");
} else {
$is_bare = _check_if_bare($repository);
if (defined $is_bare && defined $remote) {
RepoUtil::set_config("remote.$remote.bare", $is_bare);
}
$push_to_non_bare_repo = (defined $is_bare && $is_bare eq "false");
}
}
}
# Throw an error if the user is trying to push to a bare repository
# (and not using a refspec with a colon character)
if ($push_to_non_bare_repo) {
print STDERR <<EOF;
Aborting: You are trying to push to a repository with an associated working
copy, which will leave its working copy out of sync with its repository.
Rather than pushing changes to that repository, you should go to where that
repository is located and pull changes into it (using eg pull). If you
know what you are doing and know how to deal with the consequences, you can
override this check by explicitly specifying source and destination
references, e.g.
eg push REMOTE BRANCH:REMOTE_BRANCH
Please refer to
eg help topic refspecs
to learn what this syntax means and what the consequences of overriding this
check are.
EOF
exit 1;
}
#
# Get the default branch to push to, if needed
#
if (!@branches && !@tags && !@git_refspecs && !$default_specified) {
# User hasn't specified what to push; default choices:
# 1 - remote.$remote.(push|mirror) options
my $default_known = 0;
if (defined($remote)) {
if (defined(RepoUtil::get_config("remote.$remote.push")) ||
defined(RepoUtil::get_config("remote.$remote.mirror"))) {
$default_known = 1;
}
}
# 2 - branch.$branch.merge option
if (!$default_known) {
my $branch = RepoUtil::current_branch();
$push_branch = RepoUtil::get_config("branch.$branch.merge");
if (defined $push_branch) {
$push_branch =~ s#refs/heads/##;
push(@{$self->{args}}, "$branch:$push_branch");
$default_known = 1;
}
}
# 3 - the only branch that exists at the remote end
if (!$default_known && defined $repository) {
my $only_branch = RepoUtil::get_only_branch($repository, "push");
push(@{$self->{args}}, $only_branch);
}
}
#
# Get the branch(es) to push
#
push(@branches, @git_refspecs);
push(@{$self->{args}}, @branches);
foreach my $tag (@tags) {
push(@{$self->{args}}, ("tag", $tag));
}
}
sub run {
my $self = shift;
@args = Util::quote_args(@{$self->{args}});
return ExecUtil::execute("$git_cmd push @args", ignore_ret => 1);
}
###########################################################################
# rebase #
###########################################################################
package rebase;
@rebase::ISA = qw(subcommand);
INIT {
$command{rebase} = {
extra => 1,
section => 'timesavers',
about => "Port local commits, making them be based on a different\n" .
" repository version"
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
initial_commit_error_msg => "Error: No recorded commits to rewrite.",
@_);
bless($self, $class);
#
# Note: Parts of help were taken from the git-rebase manpage, which
# was also available under GPLv2.
#
$self->{'help'} = "
Usage:
eg rebase [-i | --interactive] [ --since SINCE ] [ --onto ONTO ]
[ --against AGAINST ] [BRANCH_TO_REBASE]
eg rebase [ --continue | --skip | --abort ]
Description:
Rewrites commits on a branch, making them be based on a different
repository version. Technically, the old commits are not overwritten or
deleted (only new ones are written), meaning that other branches sharing
the same commits will be unaffected and users can undo a rebase (until
the unused commits are cleaned up after a few weeks).
WARNING:
Rebasing commits in a branch is an advanced operation which changes
history in a way that will cause problems for anyone who already has a
copy of the branch in their repository when they try to pull updates
from you. This may cause them to experience many conflicts in their
merges and require them to resolve those conflicts manually, or rewrite
their own history, or even toss out their changes and simply accept
your version. (The last of those options is common enough that there
is a special method of pulling and pushing changes in such cases; see
'eg help topic refspecs' for more details.)
Non-interactive rebase (running without the --interactive or -i flags):
Specifying which commits to rewrite and what to rewrite them relative
to involves specifying up to three branches or revisions: SINCE, ONTO,
and BRANCH_TO_REBASE. eg will take all commits in the BRANCH_TO_REBASE
branch that are not in the SINCE branch, and record them as commits on
top of the tip of the ONTO branch. The ONTO and SINCE branches are not
changed by this operation. The BRANCH_TO_REBASE branch is changed to
record the tip of the newly written branch.
See also the \"If a conflict occurs\" section below.
Interactive rebase (running with the --interactive or -i flag):
Interactive rebasing allows you a chance to edit the commits which are
rebased, including
* reordering commits
* removing commits
* combining multiple commits into one commit
* amending commits to include different changes or log messages
* splitting one commit into multiple commits
When running interactively, eg rebase will begin by making a list of
the commits which are about to be rebased and allow you to change the
the list before rebasing. The list will include one commit per line,
allowing you to
* reorder commits by reordering lines
* removing commits by removing lines
* combining multiple commits into one, by changing 'pick' to 'squash'
at the beginning of each line of the commits you want combined
*except* the first
* amend a commit by changing the 'pick' at the beginning of the line
of the relevant commit to 'edit'. This will make eg rebase stop
after applying that commit, allowing you to make changes and run
'eg commit --amend' followed by 'eg rebase --continue'.
* split one commit into multiple commits by changing 'pick' at the
beginning of the line of the relevant commit to 'edit'. This will
make eg rebase stop *after* applying that commit, allowing you to
manually undo that commit while keeping the changes in the working
copy (with 'eg reset HEAD~1') and then make multiple commits (with
'eg commit') before running 'eg rebase --continue'. Note that eg
stash may come in handy for testing the split commits.
If a conflict occurs:
Rebase will stop at the first problematic commit and leave conflict
markers (<<<<<<) in the tree. You can use eg status and eg diff to
find the problematic files and locations. Once you edit the files to
fix the conflicts, you can run
eg resolved FILE
to mark the conflicts in FILE as resolved. Once you have resolved all
conflicts, you can run
eg rebase --continue
If you simply want to skip the problematic patch (and end up with one
less commit), you can instead run
eg rebase --skip
Alternatively, to abort the rebase and return to your previous state,
you can run
eg rebase --abort
Examples:
Take a branch named topic that was split off of the master branch, and
update it to be based on the new tip of master.
\$ eg rebase --since master --onto master topic
Pictorally, this changes:
A---B---C topic
/
D---E---F---G master
into
A'--B'--C' topic
/
D---E---F---G master
Same as the the above example, with less typing
\$ eg rebase --against master topic
Same as the last two examples, assuming topic is the current branch
\$ eg rebase --against master
Take a branch named topic that is based off of a branch named next, which
is in turn based off master, and rewrite topic so that it appears to be
based off the most recent version of master.
\$ eg rebase --since next --onto master topic
Pictorally, this changes
o---o---o---o---o master
\\
o---o---o---o---o next
\\
o---o---o topic
into
o---o---o---o---o master
| \\
| o'--o'--o' topic
\\
o---o---o---o---o next
Take just the last two commits of the current branch, and rewrite them
to be relative to the commit just before the most recent on the master
branch.
\$ eg rebase --since current~2 --onto master~1 current
Pictorally, this changes:
A---B---C---D---E current
/
F---G---H---I---J---K master
into
D'---E' current
/
F---G---H---I---J---K master
Reorder the last two commits on the current branch
\$ eg rebase --interactive --since HEAD~2
(Then edit the file you are presented with and change the order of the
two lines beginning with 'pick')
Pictorally, this changes:
A---B---C---D---E---F master
into
A---B---C---D---F'---E' master
Options:
--since SINCE
Upstream branch to compare against; only commits not found in this
branch will be rebased. Note that if --onto is not specified, the
value of SINCE will be used for that as well.
The value of SINCE is not restricted to existing branch names; any
valid revision can be used (due to the fact that all revisions know
their parents and a revision plus its ancestors can define a branch).
--onto ONTO
Starting point at which to create the new commits. If the --onto
option is not specified, the starting point is whatever is provided by
the --since option. Any valid revision can be used for the value of
ONTO.
--against AGAINST
An alias for --since AGAINST, provided to make command lines clearer
when the --onto flag is not also used. (Typically, --against is used
if --onto is not, and --since is used if --onto is, but --against and
--since can be used interchangably.)
--interactive, -i
Make a list of the revisions which are about to be rebased and let the
user edit that list before rebasing. Can be used to split, combine,
remove, insert, reorder, or edit commits.
--continue
Restart the rebasing process after resovling a conflict
--skip
Restart the rebasing process by skipping the current patch (resulting
in a rewritten history with one less commit).
--abort
Abort the stopped rebase operation and restore the original branch
";
$self->{'differences'} = "
The only differences between eg rebase and git rebase are cosmetic;
further, eg rebase accepts all options and flags that git rebase accepts.
eg adds the identically behaved flags --since and --against in
preference to using the position of the branch/revision name on the
command line. Note that
git rebase master
is somewhat confusing in that it isn't rebasing master but the current
branch. To make this clearer, eg allows (and encourages) the form
eg rebase --against master
The reason that both --against and --since flags were added (with
identical behavior), is that the former makes for clearer command lines
when the --onto flag is not also used.
";
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
$self->{args} = [];
my $record_arg = sub { push(@{$self->{args}}, "--$_[0]"); };
my $record_args = sub { $_[0] = "--$_[0]"; push(@{$self->{args}}, @_); };
my $since;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"interactive|i" => sub { &$record_arg(@_) },
"verbose|v" => sub { &$record_arg(@_) },
"merge|m" => sub { &$record_arg(@_) },
"C=i" => sub { &$record_args(@_) },
"whitespace=s" => sub { push(@{$self->{args}},"--whitespace=$_[1]") },
"preserve-merges|p" => sub { &$record_arg(@_) },
"onto=s" => sub { &$record_args(@_) },
"against=s" => sub { $since=$_[1] },
"since=s" => sub { $since=$_[1] },
"continue" => sub { &$record_arg(@_) },
"skip" => sub { &$record_arg(@_) },
"abort" => sub { &$record_arg(@_) },
);
die "Too many branches/revisions specified\n"
if @ARGV > 1 && defined $since;
push(@{$self->{args}}, $since) if defined $since;
push(@{$self->{args}}, @ARGV);
}
sub run {
my $self = shift;
@args = Util::quote_args(@{$self->{args}});
return ExecUtil::execute("$git_cmd rebase @args", ignore_ret => 1);
}
###########################################################################
# remote #
###########################################################################
package remote;
@remote::ISA = qw(subcommand);
INIT {
$command{remote} = {
unmodified_behavior => 1,
extra => 1,
section => 'collaboration',
about => 'Manage named remote repositories',
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg remote
eg remote add REMOTENAME URL
eg remote rm REMOTENAME
eg remote update GROUPNAME
Description:
eg remote is a convenience utility to make it easy to track changes from
multiple remote repositories. It is used to
1) Set up
REMOTENAME -> URL
aliases that can be used in the place of full urls to simplify
commands such as push or pull
2) Pulling updates from multiple branches of a remote repository at
once and storing them in remote tracking branches (which differ from
normal branches only in that they have a prefix of REMOTENAME/ in
their name).
3) Pulling updates from multiple branches of multiple remote
repositories at once, storing them all in remote tracking branches.
Examples:
The examples section is split into three categories:
1) Managing which remotes exist:
2) Using one or more existing remotes
3) Using remote tracking branches created through usage of remotes
Category 1: Managing which remotes exist:
List which removes exist
\$ eg remote
or, list remotes and their urls (among other things)
\$ eg info
Add a new remote for the url ssh://some.machine.org//path/to/repo.git,
giving it the name jim
\$ eg remote add jim ssh://some.machine.org//path/to/repo.git
Add a new remote for the url git://composit.org//location/eyecandy.git,
giving it the name bling
\$ eg remote add bling git://composit.org//location/eyecandy.git
Delete the remote named bob, and remove all related remote tracking
branches (i.e. those branches whose names begin with \"bob/\"), as well
as any associated configuration settings
\$ eg remote rm bob
Category 2: Using one or more existing remotes
Pull updates for all branches of the remote jill, storing each in a
remote tracking branch of the local repository named jill/BRANCH.
\$ eg fetch jill
Pull changes from the magic branch of the remote merlin and merge it
into the current branch (i.e. standard pull behavior) AND also update
all remote tracking branches associated with the remote (i.e. act as if
'eg fetch merlin' was also run)
\$ eg pull --branch magic merlin
Grab updates from all remotes, i.e. run 'eg fetch REMOTE' for each
remote.
\$ eg remote update
(Technically, some remotes could be manually configured to be excluded
from this update.)
Grab updates from all remotes in the group named friends (created by
use of 'eg config remotes.friends \"REMOTE1 REMOTE2...\"'), i.e. run
'eg fetch REMOTE' for each remote in the friends group
\$ eg remote update friends
Category 3: Using remote tracking branches created through usage of remotes
List all remote tracking branches
\$ eg branch -r
Merge the remote tracking branch jill/stable into the current branch
\$ eg merge jill/stable
Get a history of the changes on the bling/explode branch
\$ eg log bling/explode
Create a new branch named my-testing based off of the remote tracking
branch jenny/testing
\$ eg branch my-testing jenny/testing
";
return $self;
}
###########################################################################
# reset #
###########################################################################
package reset;
@reset::ISA = qw(subcommand);
INIT {
$command{reset} = {
extra => 1,
section => 'modification',
about => 'Forget local commits and (optionally) undo their changes'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg reset [--working-copy | --no-unstaging] [REVISION]
Description:
Forgets local commits for the active branch and (optionally) undoes their
changes in the working copy. If you have staged changes (changes you
explictly marked as ready for commit) this function also unstages them by
default. See 'eg help topic staging' to learn about the staging area.
From a computer science point of view, eg reset moves the current branch
tip to point at an older commit, and also optionally changes the working
copy and staging area to match the version of the repository recorded in
the older commit.
Note that this function should be used with caution; it is often used to
discard unwanted data or to modify recent local \"history\" of commits.
You want to be careful to not also discard wanted data, and modifying
history is a bad idea if someone has already obtained a copy of that
local history from you (rewriting history makes merging and updating
problematic).
Examples:
Throw away all changes since the last commit
\$ eg reset --working-copy HEAD
Note that HEAD always refers to the current branch, and the current
branch always refers to its last commit.
Throw away the last three commits and all current changes (this is a bad
idea if someone has gotten a copy of these commits from you; this should
only be done for truly local changes that you no longer want).
\$ eg reset --working-copy HEAD~3
Unrecord the last two commits, but keep the changes corresponding to these
commits in the working copy. (This can be used to fix a set of \"broken\"
commits.)
\$ eg reset HEAD~2
While working on the \"stable\" branch, you decide that the last 5 commits
should have been part of a separate branch. Here's how you retroactively
make it so:
Verify that your working copy is clean...then
\$ eg branch difficult_bugfix
\$ eg reset --working-copy HEAD~5
\$ eg switch difficult_bugfix
The first step creates a new branch that initially could be considered an
alias for the stable branch, but does not switch to it. The second step
moves the stable branch tip back 5 commits and modifies the working copy
to match. The last step switches to the difficult_bugfix branch, which
updates the working copy with the contents of that branch. Thus, in the
end, the working copy will have the same contents as before you executed
these three steps (unless you had local changes when you started, in
which case those local changes will be gone).
Stage files (mark changes in them as good and ready for commit but
without yet committing them), then change your mind and unstage all
files.
\$ eg stage foo.c bla.h
\$ eg reset HEAD
Note that using HEAD as the commit means to forget all commits since HEAD
(always an empty set) and undo any staged changes since that commit.
Options:
--working-copy
Also make the working tree match the version of the repository recorded
in the specified commit. If this option is not present, the working
copy will not be modified.
--no-unstaging
Do not modify the staging area; only change the current branch tip to
point to the older commit.
REVISION
A reference to a recorded version of the repository, defaulting to HEAD
(meaning the most recent commit on the current branch). See 'eg help
topic revisions' for more details.
";
$self->{'differences'} = '
The only differences between eg reset and git reset are cosmetic;
further, eg reset accepts all options and flags that git reset accepts.
git reset uses option names of --soft, --mixed, and --hard. While eg
reset will accept these option names for compatibility, it provides
alternative names that are more meaningful:
--working-copy <=> --hard
--no-unstaging <=> --soft
There is no alternate name for --mixed, since it is the default and thus
does not need to appear on the command line at all.
The modified revert command of eg is encouraged for reverting specific
files, though eg reset has the same file-specific reverting that git
reset does.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
my ($hard, $soft) = (0, 0);
return if (scalar(@ARGV) > 0 && $ARGV[0] eq "--");
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"--working-copy" => \$hard,
"--no-unstaging" => \$soft,
);
die "Cannot specify both --working-copy and --no-unstaging!\n"
if $hard && $soft;
unshift(@ARGV, "--hard") if $hard;
unshift(@ARGV, "--soft") if $soft;
}
###########################################################################
# resolved #
###########################################################################
package resolved;
@resolved::ISA = qw(subcommand);
INIT {
$command{resolved} = {
new_command => 1,
extra => 1,
section => 'compatibility',
about => 'Declare conflicts resolved and mark file as ready for commit'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1,
git_equivalent => 'add',
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg resolved PATH...
Description:
Declare conflicts resolved for the specified paths, and mark contents of
those files as ready for commit.
Examples
After fixing any update or merge conflicts in foo.c, declare the fixing to
be done and the contents ready to commit.
\$ eg resolved foo.c
";
$self->{'differences'} = '
eg resolved is a command new to eg that is not part of git; however, it
simply calls git add.
';
return $self;
}
sub run {
my $self = shift;
my $package_name = ref($self);
@ARGV = Util::quote_args(@ARGV);
return ExecUtil::execute("$git_cmd add @ARGV", ignore_ret => 1);
}
###########################################################################
# revert #
###########################################################################
package revert;
@revert::ISA = qw(subcommand);
INIT {
$command{revert} = {
extra => 1,
section => 'modification',
about => 'Revert local changes and/or changes from previous commits'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, git_equivalent => '', @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg revert [[-m PARENT_NUMBER] --in REVISION | --since REVISION]
[--staged | --unstaged] [--] [PATH...]
Description:
eg revert undoes edits to your files, without changing the commit history
or changing which commit is active. (If you are looking for a different
kind of 'undo'; they are discussed and contrasted below.) There are many
options for what to revert; you may want to jump ahead to the examples
section below and then come back and read the full description.
The work eg revert does includes discarding local modifications, removing
recorded conflict states, undoing add or stage operations (i.e. unstaging
files), and restoring deleted files to the previously recorded version.
If you revert changes since some revision prior to the most recent,
revert will also remove any files which were added in a later revision.
By default, eg revert will revert edits since the last commit(*). One
can specify a different revision to revert file contents back to, or
revert edits made in a single previous commit(**). (Advanced usage note:
eg revert will undo both staged and unstaged changes by default; you can
request only one of these; see 'eg help topic staging' for more details
on what staged and unstaged changes are.)
(*) For an initial or root commit, eg revert will simply undo adds. When
in an uncompleted merge state, it is an error to not specify which commit
to revert relative to (with the --since flag).
. (**) When reverting the changes made *in* a merge commit, the revert
command needs to know which parent of the merge the revert should be
relative to. This can be specified using the -m option.
To avoid accidental loss of local changes, nothing will be done when no
arguments are provided to eg revert. However, eg revert will check for
various special cases (from the different types of 'undo' below), and try
to provide an error message tailored to any special circumstances
relevant to you.
=== Comparison of different types of 'undo' available ===
* Back up or switch to an earlier commit (eg switch)
* Make a new commit to reverse the changes of a previous commit (eg
cherry-pick -R)
* Remove commits from history (eg reset OR eg rebase --interactive)
* Reverting edits, without switching commits or changing commit history
(eg revert)
* Abort an incomplete operation
* Incomplete merge: eg revert --since HEAD
* Unfinished rebase: eg rebase --abort
* Unfinished apply mail: eg am --abort
* Unfinished bisect: eg bisect reset
Examples:
Undo changes since the last commit on the current branch to bar.h and
foo.c. This can be done with either of the following methods:
\$ eg revert bar.h foo.c # Method #1
\$ eg revert --since HEAD bar.h foo.c # Method #2, more explicit
While on the bling branch, revert the changes in the last 3 commits (as
well as any local changes) to any file under the directory docs. This
can be done by:
\$ eg revert --since bling~3 docs
While on the stable branch, you determine that the seventh commit prior
to the most recent had a faulty change to foosubdir and baz.txt and you
simply want to undo it. This can be accomplished by:
\$ eg revert --in stable~7 -- foosubdir baz.txt
You decide that all changes to foobar.cpp in your working copy and in the
last 2 commits are bad and want to revert them. This is done by:
of:
\$ eg revert --since HEAD~2 -- foobar.c
You decide that some of the changes in the merge commit HEAD~4 are bad.
You would like to revert the changes to baz.py in HEAD~4 relative to its
second parent. This can be accomplished as follows:
\$ eg revert -m 2 --in HEAD~4 baz.py
(Advanced) Undo a previous stage, marking changes in foo.c as not
being ready for commit (this is equivalent to eg unstage foo.c):
\$ eg revert --staged foo.c
(Advanced) Undo changes since the most recent stage to soopergloo.f77
\$ eg revert --unstaged soopergloo.f77
(Advanced) You decide that the changes to abracadabra.xml made in commit
HEAD~8 are bad. You want to revert those changes in the version of
abracadabra.xml but only to your working copy. This is done by:
\$ eg revert --unstaged --in HEAD~8 -- abracadabra.xml
Options:
--since
Revert the changes made since the specified commit, including any local
changes. This takes the difference between the specified commit and
the current version of the files and reverses these changes.
--in
Revert the changes made in the specified commit. This takes the
difference between the parent of the specified commit and the specified
commit and reverse applies it.
REVISION
A reference to a recorded version of the repository, defaulting to HEAD
(meaning the most recent commit on the current branch). See 'eg help
topic revisions' for more details.
-m PARENT_NUMBER
When reverting the changes made in a merge commit, the revert command
needs to know which parent of the merge the revert should be relative
to. Use this flag with the parent number (1, 2, 3...) to specify which
parent commit to revert relative to.
Can only be used with the --in option.
--staged
Make changes only to the staged (explicitly marked as ready to be
committed) version of files.
--unstaged
Make changes only to the unstaged version of files, i.e. only to the
working copy.
--
This option can be used to separate command-line options and commits
from the list of files, (useful when filenames might be mistaken for
command-line options or be mistaken as a branch or tag name).
PATH...
One or more files or directories. The changes reverted will be limited
to the listed files or files below the listed directories.
";
$self->{'differences'} = '
eg revert is similar to the revert command of svn, hg, bzr, or darcs. It
is not provided by any one git command; it overlaps with about five
different git commands in specific cases. git users wanting the
functionality in eg revert will typically be guided by expert git users
towards whichever git command seems like the most natural fit for the
particular case the user asks about. Quite often, such users will
continue using the command they are given for subsequent situations...and
will often stumble across multiple cases where the git command no longer
matches the wanted revert behavior.
git does provide a command called revert, which is a subset of the
behavior of eg cherry-pick:
git revert COMMIT
is the same as
eg cherry-pick -R COMMIT
which is, modulo the automatic commit message provided by git revert, the
same as
eg revert --in COMMIT && eg commit
Note that while eg revert --in may look similar to git revert, the former
is about undoing changes in just the working copy, is typically
restricted to a specific subset of files, and is usually just one change
of many towards testing or creating something new to be committed. The
latter is always concerned with reverse applying an entire commit, and is
almost always used to immediately record that change.
Note that git revert commands are invalid syntax in eg (since eg revert
always requires the --since or --in flags to be specified whenever a
commit is). This means that eg can catch such cases and notify git
users to adopt the eg cherry-pick -R command.
Due to these changes, eg revert should be much more welcoming to users of
svn, hg, bzr, or darcs. It also provides a simple discovery mechanism
for existing git users to allow them to easily work with eg.
Additionally, these changes also make the reset and checkout/switch
subcommands of eg easier to understand by limiting their scope instead of
each having two very different capabilities. (Technically, eg reset and
eg checkout still have those capabilities for backwards compatibility, I
just omit them in the documentation.)
It seems that perhaps eg revert could be extended further, to accept
things like
\$ eg revert --in HEAD~8..HEAD~5 foo.c
to allow reverting changes made in a range of commits. The --in could
even be optional in such a case, since the range makes it clear what is
wanted.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
my $initial_commit = RepoUtil::initial_commit();
# Parsing opts
my ($staged, $unstaged, $in) = (0, 0, -1);
my $m;
my $rev;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"-m=i" => \$m,
"--staged" => \$staged,
"--unstaged" => \$unstaged,
"--in=s" => sub { $in = 1; $rev = $_[1]; },
"--since=s" => sub { $in = 0; $rev = $_[1]; },
);
# Parsing revs and files
my ($opts, $revs, $files) = RepoUtil::parse_args(@ARGV);
unshift(@$revs, $rev) if defined($rev);
#
# Big ol' safety checks and warnings
#
if (!@$revs && !@$files) {
my $files_modified = RepoUtil::files_modified();
if (-f "$self->{git_dir}/MERGE_HEAD") {
print STDERR<<EOF;
Aborting: no revisions or files specified to revert. If you want to abort
your incomplete merge, try 'eg reset --working-copy HEAD'.
EOF
exit 1;
}
elsif (-d "$self->{git_dir}/rebase-merge" ||
-d "$self->{git_dir}/rebase-apply") {
print STDERR<<EOF;
Aborting: no revisions or files specified to revert. If you want to abort
your incomplete rebase, try:
eg rebase --abort
EOF
exit 1;
}
elsif (!$files_modified && !$initial_commit) {
my $active_branch = RepoUtil::current_branch() || 'HEAD';
print STDERR<<EOF;
There are no local changes to revert and you specified no revisions to revert
(or revert back to). Please specify a revision with --in or --since.
Alternatively, if you want to modify commits instead of just the working copy
then use reset instead of revert:
If you want to undo a rebase or a merge (including a pull or update), try:
eg reset --working-copy ORIG_HEAD
If you want to undo the last commit (but keep its changes in the working copy),
try:
eg reset $active_branch~1
If you just want to amend the last commit without undoing it, make the
additional changes you want and run:
eg commit --amend
If you want to undo previous reset commands, get the appropriate reflog
reference from eg reflog (for example, using HEAD\@{1} for <REF>) and run:
eg reset --working-copy <REF>
EOF
exit 1;
}
elsif (!$initial_commit) {
print STDERR<<EOF;
Aborting: no revisions or files specified. If you want to revert and lose
all changes since the last commit, try adding the arguments
--since HEAD
to the end of your command.
EOF
exit 1;
} else {
print STDERR<<EOF;
Aborting: no files specified.
EOF
exit 1;
}
}
# Sanity checks
die "Cannot specify -m without specifying --in.\n" if !$in && defined($m);
die "Can only specify one revision\n" if @$revs > 1;
die "No revision specified after --in\n" if ($in == 1 && !@$revs);
die "No revision specified after --since\n" if ($in == 0 && !@$revs);
if ($in == -1 && @$revs) {
die "You must specify either --in or --since when specifying a revision.\n".
"(git users:) If you are used to git revert; try running\n".
" eg cherry-pick -R @ARGV\n";
}
die "Unrecognized options: @$opts\n" if @$opts;
$in = 0 if $in == -1;
if (!$staged && !$unstaged) {
$staged = 1;
$unstaged = 1;
}
# Special checks in the case of an incomplete merge to make sure we know
# what to revert back to; if no --since or --in specified then we can only
# proceed if the user is only reverting unstaged changes
if (!@$revs && $staged && -f "$self->{git_dir}/MERGE_HEAD") {
my @merge_branches = RepoUtil::merge_branches();
my $list = join(", ", @merge_branches);
print STDERR <<EOF;
Aborting: Cannot revert the changes since the last commit, since you are in
the middle of a merge and there are multiple last commits. Please add
--since BRANCH
to your flags to eg revert, where BRANCH is one of
$list
If you simply want to abort your merge and undo its conflicts, run
eg revert --since HEAD
EOF
exit 1;
}
if ($initial_commit) {
die "Cannot revert a previous commit since there are no previous " .
"commits.\n" if $in;
die "Cannot revert to a previous commit since there are no previous " .
"commits.\n" if !$in && @$revs;
}
my @quoted_files = Util::quote_args(@$files);
my @unmerged_files = `$git_cmd ls-files --full-name -u -- @quoted_files`;
if (@unmerged_files && $in) {
die "Aborting: please clear conflicts from @unmerged_files before " .
"proceeding.\n";
}
# Record needed information
$self->{staged} = $staged;
$self->{unstaged} = $unstaged;
$self->{just_recent_unstaged} = !$in && !$staged && !@$revs;
$self->{in} = $in;
$self->{revs} = "@$revs";
$self->{revs} = "HEAD" if !@$revs;
$self->{initial_commit} = $initial_commit;
if ($in) {
# Get the revision whose changes we want to revert, and its parents
Util::push_debug(new_value => 0);
my $links = ExecUtil::output(
"$git_cmd rev-list --parents --max-count=1 $self->{revs}");
Util::pop_debug();
my @list = split(' ', $links); # commit id + parent ids
# Get a symbolic name for the parent revision we will diff against
my $first_rev = $self->{revs};
my $parent = $m || 1;
$first_rev .= "^$parent";
# Reverting changes in merge commits can only be done against one parent
die "Cannot revert a merge commit without specifying a parent!\n"
if !defined($m) && @list > 2;
# Reverting relative to a parent can only be done with existing parents
if ($parent + 1 > scalar(@list)) {
die "Cannot revert the changes made in a commit that has no prior " .
"commit\n" if !defined($m);
die "The specified commit does not have $m parents; try a lower " .
"value for -m\n" if defined($m);
}
# The combination of revs to diff between
$self->{revs} = "$first_rev $self->{revs}";
}
# Determine some other stuff needed
$self->{files} = \@quoted_files;
my ($new_files, $newish_files, $revert_files);
if (!$in && !$initial_commit) {
my $revision = (@$revs) ? $revs->[0] : "HEAD";
($newly_added_files, $new_since_rev_files, $other_files) =
RepoUtil::get_revert_info($revision, @quoted_files);
} elsif ($initial_commit) {
$newly_added_files = $files;
$new_since_rev_files = [];
$other_files = [];
}
$self->{newly_added_files} = $newly_added_files;
$self->{new_since_rev_files} = $new_since_rev_files;
$self->{other_files} = $other_files;
}
sub run {
my $self = shift;
my $git_dir = RepoUtil::git_dir();
my $paths_specified = scalar(@{$self->{files}}) > 0;
my $ret = 0;
if (!$self->{in}) {
my $revision = $self->{revs};
my @newly_added_files = @{$self->{newly_added_files}};
my @new_since_rev_files = @{$self->{new_since_rev_files}};
my @other_files = @{$self->{other_files}};
my @all_files = @{$self->{files}};
#
# Case: Initial commit
#
if ($self->{initial_commit}) {
$ret = ExecUtil::execute("$git_cmd rm --cached --quiet -- " .
"@newly_added_files", ignore_ret => 1);
exit $ret if $ret;
}
#
# Set: Reverting both staged and unstaged changes
#
elsif ($self->{staged} && $self->{unstaged}) {
#
# Case: ALL staged and unstaged changes since some revision
#
if (!$paths_specified) {
if (@newly_added_files) {
# git reset is not quiet even when requested and has idiotic return
# state; if three files have conflicts and I try to reset some
# file other than those three, the command is successful but it
# spews warnings and gives a bad exit status
ExecUtil::execute("$git_cmd reset -q $revision --" .
" @newly_added_files >/dev/null",
ignore_ret => 1);
}
my ($revision_sha1, $head_sha1, $temp_ret);
if ($revision ne "HEAD") {
Util::push_debug(new_value => 0);
($temp_ret, $revision_sha1) =
ExecUtil::execute_captured("$git_cmd rev-parse --verify $revision",
ignore_ret => 1);
($temp_ret, $head_sha1) =
ExecUtil::execute_captured("$git_cmd rev-parse --verify HEAD",
ignore_ret => 1);
Util::pop_debug();
}
$ret = ExecUtil::execute("$git_cmd reset --hard $revision",
ignore_ret => 1);
exit $ret if $ret;
if ($revision ne "HEAD" && $revision_sha1 ne $head_sha1) {
# Note, cannot git reset --soft HEAD, since HEAD has changed in
# the above reset...
$ret = ExecUtil::execute("$git_cmd reset --soft HEAD\@{1}",
ignore_ret => 1);
exit $ret if $ret;
}
}
#
# Case: Selected staged and unstaged changes since some revision
#
if ($paths_specified) {
if (@newly_added_files) {
# See rant above about 'git reset is not quiet even when requested'
ExecUtil::execute("$git_cmd reset -q $revision --" .
" @newly_added_files >/dev/null",
ignore_ret => 1);
}
if (@new_since_rev_files) {
# Ugh, when --quiet doesn't actually mean "quiet".
# (Reproduce with git-1.6.0.6 on incomplete merge handling testcase)
$ret = ExecUtil::execute("$git_cmd rm --quiet --force " .
"--ignore-unmatch -- @new_since_rev_files" .
" > /dev/null",
ignore_ret => 1);
exit $ret if $ret;
}
if (@other_files) {
$ret = ExecUtil::execute("$git_cmd checkout $revision -- " .
"@other_files", ignore_ret => 1);
exit $ret if $ret;
}
}
}
#
# Set: Reverting just staged changes
#
elsif ($self->{staged}) {
if ($paths_specified) {
$ret = ExecUtil::execute("$git_cmd reset -q $revision -- @all_files",
ignore_ret => 1);
exit $ret if $ret;
} else {
$ret = ExecUtil::execute("$git_cmd read-tree $revision",
ignore_ret => 1);
exit $ret if $ret;
}
}
#
# Set: Reverting just unstaged changes
#
elsif ($self->{unstaged}) {
if ($self->{just_recent_unstaged}) {
die "Assertion failed: Paths not specified.\n" if (!$paths_specified);
$ret = ExecUtil::execute("$git_cmd checkout -- @all_files",
ignore_ret => 1);
exit $ret if $ret;
}
else {
if (@newly_added_files) {
# This results in a no-op essentially, but at least it shows the
# equivalent commands when no new_since_rev_files and no other_files
push(@other_files, @newly_added_files);
}
if (@new_since_rev_files) {
$ret = ExecUtil::execute("rm -f @new_since_rev_files",
ignore_ret => 1);
exit $ret if $ret;
}
if (@other_files || !$paths_specified) {
my ($tmp_index) = Util::quote_args("$git_dir/tmp_index");
my $git_index = "GIT_INDEX_FILE=$tmp_index ";
my $cf = "@other_files";
if (!$paths_specified) {
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
($cf) = Util::reroot_paths__from_to_files($top_dir, $cur_dir, '.');
}
$ret = ExecUtil::execute("$git_index $git_cmd checkout $revision " .
"-- $cf", ignore_ret => 1);
exit $ret if $ret;
$ret = ExecUtil::execute("rm $tmp_index");
exit $ret if $ret;
}
}
}
}
if ($self->{in}) {
# Must do unstaged changes first, or extra unknown files can "appear"
my $location_flag;
$location_flag = "" if $self->{unstaged};
$location_flag = "--cached" if $self->{staged};
$location_flag = "--index" if ($self->{staged} && $self->{unstaged});
my @files = @{$self->{files}};
my $marker = "";
$marker = "-- " if (@files);
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
my @diff_flags = ("--binary");
my @apply_flags = ("--whitespace=nowarn", "--reject");
push(@apply_flags, $location_flag) if $location_flag;
# Print out the (nearly) equivalent commands if the user asked for
# debugging information
if ($debug) {
print " >>Running: " .
"$git_cmd diff @diff_flags $self->{revs} ${marker}@files | ";
print "(cd $top_dir && " if ($top_dir ne $cur_dir);
print "$git_cmd apply @apply_flags -R";
print ")" if ($top_dir ne $cur_dir);
print "\n";
}
# Sadly, using "git diff... | git apply ... -R" doesn't quite work,
# because apply complains very loudly if the diff is empty. So,
# we have to run diff, slurp in its output, check if its nonempty,
# and then only pipe that output back out to git apply if we have
# an actual diff to revert.
if ($debug < 2) {
open(DIFF, "$git_cmd diff @diff_flags $self->{revs} ${marker}@files |");
my @output = <DIFF>;
my $diff = join("", @output);
# Listing unmerged paths doesn't count as nonempty
$diff =~ s/\* Unmerged path.*\n//g;
close(DIFF);
$ret = $?;
exit $ret >> 8 if $ret;
if ($diff) {
chdir($top_dir) if $top_dir ne $cur_dir;
open(APPLY, "| $git_cmd apply @apply_flags -R");
print APPLY $diff;
close(APPLY);
chdir($cur_dir) if $top_dir ne $cur_dir;
}
}
}
return 0;
}
###########################################################################
# rm #
###########################################################################
package rm;
@rm::ISA = qw(subcommand);
INIT {
$command{rm} = {
extra => 1,
section => 'modification',
about => 'Remove files from subsequent commits and the working copy'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg rm [-f] [-r] [--staged] FILE...
Description:
Marks the contents of the specified files for removal from the next
commit. Also removes the given files from the working copy, unless
otherwise specified with the --staged flag.
To prevent data loss, the removal will be aborted if the file has
modifications. This check can be overriden with the -f flag.
Examples:
Mark the content of the files foo and bar for removal from the next
commit, and delete these files from the working copy.
\$ eg rm foo bar
Mark the content of the file baz.c for removal from the next commit, but
keep baz.c in the working copy as an unknown file.
\$ eg rm --staged baz.c
(Advanced) Remove all *.txt files under the Documentation directory OR
any of its subdirectories. Note that the asterisk must be preceded with
a backslash to prevent standard shell expansion. (Google for 'shell
expansion' if that makes no sense to you.)
\$ eg rm Documentation/\\*.txt
Options:
-f
Override the file-modification check.
-r
Allow recursive removal when a directory name is given. Without this
option attempted removal of directories will fail.
--staged
Only remove the files from the staging area (the area with changes
marked as ready to be recorded in the next commit; see 'eg help topic
staging' for more details). When using this flag, the given files will
not be removed from the working copy and will instead become
\"unknown\" files.
--
This option can be used to separate command-line options from the list
of files, (useful when filenames might be mistaken for command-line
options).
";
$self->{'differences'} = '
eg rm is identical to git rm except that it accepts --staged as a synonym
for --cached.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
return if (scalar(@ARGV) > 0 && $ARGV[0] eq "--");
my $result = main::GetOptions("--help" => sub { $self->help() });
foreach my $i (0..$#ARGV) {
$ARGV[$i] = "--cached" if $ARGV[$i] eq "--staged";
}
}
###########################################################################
# squash #
###########################################################################
package squash;
@squash::ISA = qw(subcommand);
INIT {
$command{squash} = {
new_command => 1,
extra => 1,
section => 'modification',
about => 'Combine all changes since a given revision into a new commit'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, git_equivalent => '', @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg squash [--against REVISION]
Description:
Combines all commits since REVISION into a single commit, and open an
editor with the concatenation of log messages for the user to edit to
create a new log message.
REVISION must be an ancestor of the current commit. If REVISION is
not specified, the remote tracking branch for the current branch is
assumed. (If there is no such branch, eg squash will abort with an
error.)
Examples:
Combine all commits in the current branch that aren't in origin/master
into a single commit
\$ eg squash --against origin/master
Options:
--against
An optional command line argument that makes it clearer what is
happening. (In the example above, we are not \"squashing origin/master\",
we are squashing all changes since origin/master on top of origin/master.
";
$self->{'differences'} = '
eg squash is a command new to eg that is not part of git.
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
my $since;
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"against=s" => sub { $since=$_[1] },
);
$since = shift @ARGV if !defined($since);
die "Aborting: Too many revisions specified.\n" if @ARGV > 1;
if (!defined($since)) {
my $branch = RepoUtil::current_branch();
die "Aborting: No revision specified.\n" if !defined($branch);
$merge_remote = RepoUtil::get_config("branch.$branch.remote");
$merge_branch = RepoUtil::get_config("branch.$branch.merge");
die "Aborting: No revision specified.\n" if !defined($merge_branch);
$merge_branch =~ s#^refs/heads/##;
$since = "$merge_remote/$merge_branch";
}
$self->{since} = $since;
Util::push_debug(new_value => 0);
my ($retval, $orig_head, $since_sha1sum);
# Get the sha1sum where HEAD points now, make sure HEAD is valid
($retval, $orig_head) =
ExecUtil::execute_captured("$git_cmd rev-parse HEAD", ignore_ret => 1);
die "Aborting: You have no commits on HEAD.\n" if $retval != 0;
chomp($orig_head);
$self->{orig_head} = $orig_head;
# Get the sha1sum where $since points now, make sure it is valid
($retval, $since_sha1sum) =
ExecUtil::execute_captured("$git_cmd rev-parse $self->{since}",
ignore_ret => 1);
die "Invalid revision reference: $self->{since}\n" if $retval != 0;
chomp($since_sha1sum);
# Make sure user has no staged changes
my $output = `$git_cmd diff --cached --quiet`;
die "Aborting: You have staged changes; please commit them first.\n" if $?;
# Ensure $self->{since} is an ancestor of HEAD
my ($ret, $unmerged) =
ExecUtil::execute_captured("$git_cmd rev-list HEAD..$self->{since} | " .
" wc -l");
chomp($unmerged);
die "Aborting: $self->{since} is not an ancestor of HEAD.\n" if $unmerged;
die "Aborting: There are no commits since $self->{since}.\n"
if $orig_head eq $since_sha1sum;
Util::pop_debug();
}
sub run {
my $self = shift;
my $package_name = ref($self);
# Fill out a basic log message
my ($fh, $filename) = main::tempfile();
print $fh <<EOF;
# Please combine the following commit messages into a single commit message.
# Lines starting with a '#' will be ignored.
EOF
close($fh);
$ret = ExecUtil::execute("$git_cmd log --no-merges --pretty=format:" .
"'#commit %H%n#Author: %an <%ae>%n#Date: %ad%n%n%s%n%n%b' " .
" $self->{since}..$self->{orig_head} >> $filename");
exit $ret if $ret;
# Now, reset and commit
$ret = ExecUtil::execute("$git_cmd reset --soft $self->{since}");
exit $ret if $ret;
$ret = ExecUtil::execute("$git_cmd commit -F $filename --edit",
ignore_ret => 1);
# Restore the branch pointer if the commit failed (e.g. empty log message)
ExecUtil::execute("$git_cmd reset --soft $self->{orig_head}") if $ret != 0;
unlink($filename);
return 0;
}
###########################################################################
# stage #
###########################################################################
package stage;
@stage::ISA = qw(subcommand);
INIT {
$command{stage} = {
new_command => 1,
section => 'modification',
about => 'Mark content in files as being ready for commit'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1,
git_equivalent => 'add',
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg stage [--] PATH...
Description:
Marks the contents of the specified files as being ready to commit,
scheduling them for addition to the repository. (This is also known as
staging.) When a directory is passed, all files in that directory or any
subdirectory are recursively added.
You can use 'eg unstage PATH...' to unstage files.
See 'eg help topic staging' for more details, including situations where
you might find staging useful.
Examples:
Create a new file, and mark it for addition to the repository.
\$ echo hi > there
\$ eg stage there
(Advanced) Mark some changes as good, add some verbose sanity checking code,
then commit just the good changes.
Implement some cool new feature in somefile.C
\$ eg stage somefile.C
Add some verbose sanity checking code to somefile.C
Decide to commit the new feature code but not the sanity checking code:
\$ eg commit --staged
(Advanced) Show changes in a file, split by those that you have marked as
good and those that you haven't:
Make various edits
\$ eg stage file1 file2
Make more edits, include some to file1
\$ eg diff # Look at all the changes
\$ eg diff --staged # Look at the \"ready to be committed\" changes
\$ eg diff --unstaged # Look at the changes not ready to be commited
Options:
--
This option can be used to separate command-line options from the list
of files, (useful when filenames might be mistaken for command-line
options).
";
$self->{'differences'} = '
eg stage is a command new to eg that is not part of git (update: it is
part of newer versions of git, with identical meaning to eg). eg stage
merely calls git add.
';
return $self;
}
sub run {
my $self = shift;
my $package_name = ref($self);
@ARGV = Util::quote_args(@ARGV);
return ExecUtil::execute("$git_cmd add @ARGV", ignore_ret => 1);
}
###########################################################################
# stash #
###########################################################################
package stash;
@stash::ISA = qw(subcommand);
INIT {
$command{stash} = {
section => 'timesavers',
about => 'Save and revert local changes, or apply stashed changes',
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
initial_commit_error_msg => "Error: Cannot stash away changes when there " .
"is no commit yet.",
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg stash list [--details]
eg stash [save DESCRIPTION]
eg stash apply [DESCRIPTION]
eg stash show [OPTIONS] [DESCRIPTION]
eg stash (drop [DESCRIPTION] | clear)
Description:
This command can be used to remove any changes since the last commit,
stashing these changes away so they can be reapplied later. It can also
be used to apply any previously stashed away changes. This command can
be used multiple times to have multiple sets of changes stashed away.
Unknown files (files which you have never run 'eg stage' on) are
unaffected; they will not be stashed away or reverted.
When no arguments are specified to eg stash, the current changes are
saved away with a default description.
Examples:
You have lots of changes that you're working on, then get an important
but simple bug report. You can stash away your current changes, fix the
important bug, and then reapply the stashed changes:
\$ eg stash
fix, fix, fix, build, test, etc.
\$ eg commit
\$ eg stash apply
You can provide a description of the changes being stashed away, and
apply previous stashes by their description (or a unique substring of the
description).
make lots of changes
\$ eg stash save incomplete refactoring work
work on something else that you think will be a quick fix
\$ eg stash save longer fix than I thought
fix some important but one-liner bug
\$ eg commit
\$ eg stash list
\$ eg stash apply incomplete refactoring work
finish off the refactoring
\$ eg commit
\$ eg stash apply fix than I
etc., etc.
You want to get some details about an existing stash created above:
\$ eg stash show incomplete refactoring
\$ eg stash show -p incomplete refactoring
Options:
list [--details]
Show the saved stash descriptions. If the --details flag is present,
provide more information about each stash.
save DESCRIPTION
Save current changes with the description DESCRIPTION. The
description cannot start with \"-\".
apply [DESCRIPTION]
Apply the stashed changes with the specified description. If no
description is specified, and more than one stash has been saved, an
error message will be shown. The description cannot start with \"-\".
show [OPTIONS] [DESCRIPTION]
Show the stashed changes with the specified description. If no
description is specified, and more than one stash has been saved, an
error message will be shown. The description cannot start with \"-\".
Note that the output shown is the output from diff --stat. If you
want the full patch, pass the -p option. Other options for
controlling diff output (such as --name-status or --dirstat, see
'git help diff') are also possible options.
drop [DESCRIPTION]
Delete the specified stash. The description cannot start with
\"-\".
clear
Delete all stashed changes.
";
$self->{'differences'} = '
eg stash is only cosmetically different than git stash, and is fully
backwards compatible.
eg stash list, by default, only shows the saved description -- not
the reflog syntax or branch the change was made on.
eg stash apply and eg stash show also accept any string and will
apply or show the stash whose description contains that string.
Although stash and apply accept reflog syntax (like their git stash
counterparts), i.e. while
$ eg stash apply stash@{3}
will work, I think it will be easier for the user to run
$ eg stash apply rudely interrupted changes
';
return $self;
}
sub preprocess {
my $self = shift;
my $package_name = ref($self);
#
# Parse options
#
my @args;
my $result=main::GetOptions("--help" => sub { $self->help() });
# Get the (sub)subcommand
if (scalar @ARGV == 0) {
$self->{subcommand} = 'save';
} else {
$self->{subcommand} = shift @ARGV;
push(@args, $self->{subcommand});
if ($self->{subcommand} eq 'apply' && @ARGV > 0 && $ARGV[0] eq '--index') {
push(@args, shift @ARGV);
}
if ($self->{subcommand} eq 'save' &&
@ARGV > 0 && $ARGV[0] eq '--keep-index') {
push(@args, shift @ARGV);
}
if ($self->{subcommand} eq 'show') {
while(@ARGV > 0 && $ARGV[0] =~ /^-/) {
push(@args, shift @ARGV);
}
}
if ($self->{subcommand} eq 'branch') {
push(@args, shift @ARGV); # Pull off the branch name
}
}
# Show a help message if they picked a bad stash subaction.
my @valid_commands = qw(list show apply clear save drop pop branch create);
if (! grep {$_ eq $self->{subcommand}} @valid_commands) {
print STDERR<<EOF;
Aborting; invalid stash subcommand: $self->{subcommand}
EOF
exit 1;
}
# Translate the description passed to apply or show into a reflog reference
my @commands_accepting_existing_stash = qw(show drop pop apply branch);
if ((grep {$_ eq $self->{subcommand}} @commands_accepting_existing_stash) &&
scalar @ARGV > 0) {
my $stash_description = "@ARGV";
@ARGV = ();
if ($stash_description =~ m#^stash\@\{[^\{]+\}$#) {
push(@args, $stash_description)
} else {
# Will need to compare arguments to existing stash descriptions...
print " >>Getting stash descriptions to compare to arguments:\n"
if $debug;
my ($retval, $output) =
ExecUtil::execute_captured("$eg_exec stash list --refs");
my @lines = split('\n', $output);
my %refs;
my %bad_refs;
while (@lines) {
my $desc = shift @lines;
my $ref = shift @lines;
$bad_refs{$desc}++ if defined $refs{$desc};
$refs{$desc} = $ref;
}
# See if the stash description matches zero, one, or more existing
# stash descriptions; convert it to a reflog entry if only one
my @matches = grep {$_ =~ m#\Q$stash_description\E#} (keys %refs);
if (scalar @matches == 0) {
die "No stash matching '$stash_description' exists! Aborting.\n";
} elsif (scalar @matches == 1) {
# Only one regex match; use it
$stash_description = $matches[0];
} else {
# See if our string matches one stash description exactly; if so,
# we can use it.
if (!grep {$_ eq $stash_description} (keys %refs)) {
die "Stash description '$stash_description' matches multiple " .
"stashes:\n " . join("\n ", @matches) . "\n" .
"Aborting.\n";
}
}
die "Stash description '$stash_description' matches multiple stashes.\n"
if $bad_refs{$stash_description};
push(@args, $refs{$stash_description});
}
} elsif ($self->{subcommand} eq 'list' && @ARGV) {
my $arg = shift @ARGV;
if ($arg eq '--refs') {
$self->{show_refs} = 1;
} elsif ($arg eq '--details') {
$self->{show_details} = 1;
} else {
unshift(@ARGV, $arg);
}
}
# Add any unprocessed args to the arguments to use
push(@args, @ARGV);
# Reset @ARGV with the built up list of arguments
@ARGV = @args;
}
sub postprocess {
my $self = shift;
my $output = shift;
if ($debug == 2) {
print " >>(No commands to run, just data to print)<<\n";
return;
}
my @lines = split('\n', $output);
if ($self->{subcommand} eq 'list') {
my $regex =
qr#(stash\@\{[^\}]+\}): (?:WIP )?[Oo]n [^ ]*: (?:[0-9a-f]+\.\.\. )?#;
foreach my $line (@lines) {
if ($self->{show_details}) {
print "$line\n";
} else {
$line =~ s/$regex//;
print "$line\n";
print "$1\n" if $self->{show_refs};
}
}
} else {
foreach my $line (@lines) {
print "$line\n";
}
}
}
###########################################################################
# status #
###########################################################################
package status;
@status::ISA = qw(subcommand);
INIT {
$command{status} = {
section => 'discovery',
about => 'Summarize current changes'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg status
Description:
Show the current state of the project. In addition to showing the
currently active branch, this command will list files with content in any
of the following states:
Unknown files
Files that are not explicitly ignored (i.e. do not appear in an
ignore list such as a .gitignore file) but whose contents are still
not tracked by git.
These files can become known by running 'eg stage FILENAME', or
ignored by having their name added to a .gitignore file.
Newly created unknown files
Same as unknown files; the reason for splitting unknown files into
two sets is to make it easier to find the files users are more
likely to want to add. Also, 'eg commit' will by default error out
with a warning message if there are any newly created unknown files
in order to prevent forgetting to add files that should be included
in a commit.
Modified submodules:
subdirectories that are tracked under their own git repository, and
that are being tracked via use of the 'git submodule' command.
Changed but not updated (\"unstaged\")
Files whose contents have been modified in the working copy.
(Advanced usage note) If you explicitly mark all the changes in a
file as ready to be committed, then the file will not appear in this
list and will instead appear in the \"staged\" list (see below).
However, a file can appear in both the unstaged and staged lists if
only part of the changes in the file are marked as ready for commit.
Changes ready to be committed (\"staged\")
Files with content changes that have explicitly been marked as ready
to be committed. This state only typically appears in advanced
usage.
Files enter this state through the use of 'eg stage'. Files can
return to the unstaged state by running 'eg unstage' See 'eg help
topic staging' to learn about the staging area.
";
$self->{'differences'} = "
eg status output is essentially just a streamlined and cleaned version of
git status output, with the addition of a new section (newly created
untracked files) and an extra status message being displayed when in the
middle of a special state (am, bisect, merge, or rebase).
The streamlining serves to avoid information overload to new users (which
is only possible with a less error prone \"commit\" command) and the
cleaning (removal of leading hash marks) serves to make the system more
inviting to new users.
A slight wording change was done to transform \"untracked\" to \"unknown\"
since, as Havoc pointed out, the word \"tracked\" may not be very self
explanatory (in addition to the real meaning, users might think of:
\"tracked in the index?\", \"related to remote tracking branches?\", \"some
fancy new monitoring scheme unique to git that other vcses do not have?\",
\"is there some other meaning?\"). I do not know if \"known\" will fully
solve this, but I suspect it will be more self-explanatory than
\"tracked\".
There are also slight changes to the section names to reinforce
consistent naming when referring to the same concept (staging, in this
case), but the changes are very slight.
The extra status message when in the middle of an am, bisect, merge,
or rebase serves two purposes: to remind users that they are in the
middle of some operation (some people don't use the special prompt
from git's bash-completion support), and to provide a command users
can run to get help resolving such situations. (Many users were
confused about or unaware how to resolve incomplete merges and
rebases; providing them with a specially written help page they
could access seemed to effectively assist them figure out the
appropriate steps to take -- especially in tricky or special cases.)
";
return $self;
}
sub preprocess {
my $self = shift;
Getopt::Long::Configure("permute");
my $result = main::GetOptions(
"--help" => sub { $self->help() },
"v" => sub { print STDERR "-v option is not supported.\n"; exit 1 },
);
}
sub run {
my $self = shift;
my $package_name = ref($self);
$self->{special_state} = RepoUtil::get_special_state($self->{git_dir});
@ARGV = Util::quote_args(@ARGV);
-t STDOUT and $ENV{"GIT_PAGER_IN_USE"}=1;
return ExecUtil::execute("$git_cmd status @ARGV", ignore_ret => 1);
}
sub postprocess {
my $self = shift;
my $output = shift;
if ($debug == 2) {
print " >>(No commands to run, just data to print)<<\n";
return;
}
my $branch;
my $initial_commit = 0;
my %files = ( unknown => undef, unstaged => undef, staged => undef );
my @basic_info;
# Exit early if git status had an error
if ($output =~ m/^fatal:/) {
print STDERR $output;
exit 128;
}
# Parse the output
my @lines = split('\n', $output);
my $cur_state = -1;
while (@lines) {
my $line = shift @lines;
my $section = undef;
if ($line =~ m/^# On branch (.*)$/) {
$branch = $1;
} elsif ($line =~ m/^# Initial commit$/) {
$initial_commit = 1;
} elsif ($line =~ m/^# Untracked files:$/) {
$cur_state = 1;
$section = 'unknown';
$title = "Unknown files:";
} elsif ($line =~ m/^# Modified submodules:$/) {
$cur_state = 3;
$section = 'submodules';
$title = "Modified submodules:";
} elsif ($line =~ m/^# Changes to be committed:$/) {
$cur_state = 2;
$section = 'staged';
$title = 'Changes ready to be committed ("staged"):';
} elsif ($line =~ m/^# Changed but not updated:$/) {
$cur_state = 2;
$section = 'unstaged';
$title = 'Changed but not updated ("unstaged"):';
} elsif ($cur_state < 0) {
next if $line !~ m/^# (.+)/;
push(@basic_info, $1);
}
# If we're inside a section type, parse it
if ($cur_state > 0) {
my @section_files;
my $hints;
# Parse the hints first
$line = shift @lines;
while ($line =~ m/^#\s+\(use ".*/) {
$hints .= $line;
$line = shift @lines;
}
die("Bug parsing git status output") if $line ne '#';
$line = shift @lines; # Get rid of blank line
while (defined $line && $line =~ m/^#.+$/) {
if ($cur_state == 1) {
if ($line =~ m/^#(\s+)(.*)/) {
my $file = $2;
push @section_files, "$1$file";
}
} elsif ($cur_state == 2) {
if ($line =~ m/^#(\s+.*:\s+)(.*)/) {
my $file = $2;
push(@section_files, "$1$file");
}
} elsif ($cur_state == 3) {
if ($line =~ m/^#\s(.*)/) {
my $sub_info = $1;
push(@section_files, "$sub_info");
}
}
$line = shift @lines;
}
if (defined($files{$section})) {
push(@{$files{$section}{'file_list'}}, @section_files);
} else {
$files{$section} = { title => $title,
hint => $hints,
file_list => \@section_files };
}
# Record that we finished parsing this section
$cur_state = 0;
}
}
# Print out the branch we are on
if (defined $branch) {
print "(On branch $branch";
print ", no commits yet" if $initial_commit;
print ")\n";
}
foreach my $line (@basic_info) {
print "($line)\n";
}
# Print out info about any special state we're in
my $notice = "";
if (defined $self->{special_state}) {
my ($highlight, $reset) = ("", "");
if (-t STDOUT) {
$highlight=`$git_cmd config --get-color color.status.header "red reverse"`;
$reset=`$git_cmd config --get-color "" "reset"`;
}
$notice .= "($highlight";
$notice .= "YOU ARE IN THE MIDDLE OF A $self->{special_state}; ";
$notice .= "RUN 'eg help topic middle-of-";
if ($self->{special_state} eq "APPLY MAIL OR REBASE") {
# FIXME: How do we get into this state anyway, and what should they run?
# Well, printing nothing will just get them the general topic page, then
# they can pick between am and rebase
} elsif ($self->{special_state} =~ /REBASE$/) {
$notice .= "rebase";
} elsif ($self->{special_state} eq "APPLY MAIL") {
$notice .= "am";
} elsif ($self->{special_state} eq "MERGE") {
$notice .= "merge";
} elsif ($self->{special_state} eq "BISECT") {
$notice .= "bisect";
}
$notice .= "' FOR MORE INFO.";
$notice .= "$reset)\n";
print $notice;
}
# Split the unknown files into those that are newly created and those that
# have been around
if (defined($files{'unknown'})) {
# Get the list of unknown files that have been around for a while
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
my %old_unknown;
if (-f "$git_dir/info/ignored-unknown") {
my @old_unknown_files = `cat "$git_dir/info/ignored-unknown"`;
chomp(@old_unknown_files);
@old_unknown_files =
Util::reroot_paths__from_to_files($top_dir, $cur_dir, @old_unknown_files);
map { $old_unknown{$_} = 1 } @old_unknown_files;
}
my @new_unknowns;
my @old_unknowns;
foreach my $fileline (@{$files{'unknown'}{'file_list'}}) {
$fileline =~ m#(\s+(?:\e\[.*?m)?)(.*?)((?:\e\[m)?)$# ||
die "Failed parsing git status output: '$fileline'\n";
if ($old_unknown{$2}) {
push(@old_unknowns, $fileline);
} else {
push(@new_unknowns, $fileline);
}
}
if (@new_unknowns) {
$files{'new_unknowns'} = { title => 'Newly created unknown files:',
file_list => \@new_unknowns };
}
if (@old_unknowns) {
$files{'old_unknowns'} = { title => 'Unknown files:',
file_list => \@old_unknowns };
}
}
# Print out all the various changes
my $linecount = 0;
foreach my $section ('staged', 'unstaged', 'submodules',
'new_unknowns', 'old_unknowns') {
if (defined($files{$section})) {
print "$files{$section}{'title'}\n";
$linecount += 1;
foreach my $fileline (@{$files{$section}{'file_list'}}) {
print "$fileline\n";
$linecount += 1;
}
}
}
# Repeat the notice so users will see it
if (defined $self->{special_state} && $linecount > 0) {
print $notice;
}
}
###########################################################################
# switch #
###########################################################################
package switch;
@switch::ISA = qw(subcommand);
INIT {
$command{switch} = {
new_command => 1,
section => 'projects',
about => 'Switch the working copy to another branch'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
git_equivalent => 'checkout',
initial_commit_error_msg => "Error: Cannot create or switch branches " .
"until a commit has been made.",
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg switch BRANCH
eg switch REVISION
Description:
Switches the working copy to another branch, or to another tag or
revision. (Switch is an operation that can be done locally, without any
network connectivity).
To list, create, or delete branches to switch to, use eg branch. To
list, create, or delete tags to switch to, use eg tag. To list, create,
or delete revisions, use eg log, eg commit, or eg reset, respectively.
:-)
Examples:
Switch to the 4.8 branch
\$ eg switch 4.8
Switch the working copy to the v4.3 tag
\$ eg switch v4.3
";
$self->{'differences'} = '
eg switch is a subset of the functionality of git checkout; the abilities
and flags for creating and switching branches are identical between the
two, just the name of the function is different.
The ability of git checkout to get older versions of files is not part of
eg switch; instead that ability can be found with eg revert.
';
return $self;
}
sub preprocess {
my $self = shift;
if (scalar(@ARGV) == 0) {
print STDERR<<EOF;
No branch (or revision) to switch to specified! See the help for eg switch
and eg branch. The following branches exist, with the current branch marked
with an asterisk:
EOF
my $branch_obj = "branch"->new();
$branch_obj->run();
exit 1;
}
# Don't let them try to use eg switch to check out older revisions of files;
# this is just supposed to be a subset of git checkout
if (!grep {$_ =~ /^-/} @ARGV) {
die "Invalid arguments to eg switch: @ARGV\n" if @ARGV > 1;
Util::push_debug(new_value => 0);
my $valid_ref = RepoUtil::valid_ref($ARGV[0]);
Util::pop_debug();
die "Invalid branch/revision reference: $ARGV[0]\n" if !$valid_ref;
}
$self->SUPER::preprocess();
}
sub run {
my $self = shift;
my $package_name = ref($self);
@ARGV = Util::quote_args(@ARGV);
return ExecUtil::execute("$git_cmd checkout @ARGV", ignore_ret => 1);
}
###########################################################################
# tag #
###########################################################################
package tag;
@tag::ISA = qw(subcommand);
INIT {
$command{tag} = {
unmodified_behavior => 1,
extra => 1,
section => 'modification',
about => 'Provide a name for a specific version of the repository'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, @_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg tag TAG [REVISION]
eg tag -d TAG
Description:
Create or delete a tag (i.e. a nickname) for a specific version of the
project. (Tags can also be annotated or digitally signed; see the 'See
Also section.)
Note that tags are local; creation of tags in a remote repository can be
accomplished by first creating a local tag and then pushing the new tag
to the remote repository using eg push.
Examples
List the available local tags
\$ eg tag
Create a new tag named good-version for the last commit.
\$ eg tag good-version
Create a new tag named version-2.0.3 for 3 versions before the last commit
(assuming one is on a branch named project-2.0)
\$ eg tag version-2.0.3 project-2.0~3
Delete the tag named gooey
\$ eg tag -d gooey
Create a new tag named look_at_me in the default remote repository
\$ eg tag look_at_me
\$ eg push --tag look_at_me
Options:
-d
Delete the specified tag
";
return $self;
}
###########################################################################
# unstage #
###########################################################################
package unstage;
@unstage::ISA = qw(revert);
INIT {
$command{unstage} = {
new_command => 1,
extra => 1,
section => 'modification',
about => 'Mark changes in files as no longer ready for commit'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 1, git_equivalent => '', @_);
bless($self, $class);
unshift(@ARGV, "--") if scalar(@ARGV) > 0 && $ARGV[0] ne "--";
unshift(@ARGV, "--staged");
$self->{'help'} = "
Usage:
eg unstage [--] PATH...
Description:
Marks the changes in the specified files as not being ready to commit.
When a directory is passed, all files in that directory or any
subdirectory are recursively unstaged.
Note that this command is equivalent to 'eg revert --staged PATH...'
See 'eg help topic staging' for more details, including situations where
you might find staging useful.
Examples:
Create a new file, and mark it for addition to the repository, then change
your mind
\$ echo hi > there
\$ eg stage there
\$ eg unstage there
Modify an existing file, mark the modified version as being ready for commit,
then change your mind
\$ echo some extra info at end of file >> foo
\$ eg stage foo
\$ eg unstage foo
";
$self->{'differences'} = '
eg unstage is a command new to eg that is not part of git; it is implemented
on top of eg revert --staged, though it could as easily simply call through
to git reset.
';
return $self;
}
# unstage inherits from revert, and simply modifies @ARGV in new(), so that
# revert will get run with the right arguments
###########################################################################
# update #
###########################################################################
package update;
@update::ISA = qw(subcommand);
INIT {
$command{update} = {
new_command => 1,
extra => 1,
section => 'compatibility',
about => 'Use antiquated workflow for refreshing working copy, if safe'
};
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(
git_repo_needed => 1,
git_equivalent => 'pull',
@_);
bless($self, $class);
$self->{'help'} = "
Usage:
eg update
Description:
Gets updates from the default remote repository if updating is safe, and
provides suggestions on proceeding otherwise.
eg update does not accept any options...other than --help.
Examples:
Get any updates from the remote repository
\$ eg update
";
$self->{'differences'} = '
eg update is unique to eg; it exists primarily to ease the transition for
cvs/svn users and to do something useful for them. In particular, eg
update is used just to do fast-forward updates when there are no local
changes; if anything more than this is needed, eg advises users to run
other commands.
Here are the special cases eg update detects and provides tailored
messages for:
* User has local commits => ask user to use eg pull instead
* User provides argument to update => tell user to use eg switch for
checking out an older revision or
eg revert to undo changes to a file
* User has locally deleted files => tell user to use eg revert to
undo local changes (and that they do
not need to delete the file first as
they did with cvs)
* User has local modifications => Tell user to stash or commit their
changes before pulling updates
* No default repository to contact => Tell user to run "eg remote add
origin REPOSITORY_URL"
* branch.BRANCH.merge not set and => Warn user that we do not know which
more than one remote branch branch to pull from and suggest eg
present pull or setting branch.BRANCH.merge
';
return $self;
}
sub preprocess {
my $self = shift;
# Check for the --help arg
my $result=main::GetOptions("--help" => sub { $self->help() });
# Abort if the user specified any args other than --help
if (@ARGV) {
print STDERR <<EOF;
Aborting: No arguments to update are allowed. If you are trying to switch
to a different revision, use eg switch. If you are trying to undo the changes
to a particular file, use eg revert.
EOF
exit 1;
}
# Check if there are local changes
my $status = RepoUtil::commit_push_checks();
my $has_changes = $status->{has_staged_changes} ||
$status->{has_unstaged_changes} || $status->{has_unmerged_changes};
if ($has_changes) {
print STDERR <<EOF;
Aborting: You have local changes, and pulling updates could put your
working copy in a nonworking state. Consider committing your changes
before updating, or using eg stash to stash the changes away and reapply
them after the update.
EOF
if ($status->{output} =~ /^\s+deleted:/m) {
print STDERR "\n";
print STDERR <<EOF;
NOTE: If you are trying to undo the changes in a file, just run
eg revert FILE
This works whether or not the file has been deleted.
EOF
}
exit 1;
}
if ($debug) {
print " >>Commands to determine where to update from:\n";
}
# Check if there is a default repository to pull from
# <This code mostly taken from pull, but "origin" serves as extra backup>
my $branch = RepoUtil::current_branch() || "HEAD";
my $repo = RepoUtil::get_default_push_pull_repository();
$self->{repository} = $repo;
$self->{local_branch} = $branch;
# Check if there is a default branch to pull
my $merge_branch = RepoUtil::get_config("branch.$branch.merge");
if (!$merge_branch) {
# Check if the remote repository has exactly 1 branch...if so, return it,
# otherwise throw an error
my ($quoted_repo) = Util::quote_args("$self->{repository}");
my ($ret, $output) =
ExecUtil::execute_captured("$git_cmd ls-remote -h $quoted_repo");
if ($ret == 0) {
my @remote_refs = split('\n', $output);
if (@remote_refs == 1) {
# git ls-remote -h output changed at some point to include the sha1sum;
# we only want the refspec
if ($remote_refs[0] =~ /^[0-9a-f]+\s+(.*)/) {
$merge_branch = $1;
} else {
$merge_branch = $remote_refs[0];
}
}
}
}
if (!$merge_branch) {
print STDERR <<EOF;
Error: It is not clear which remote branch to update from.
You can either use eg pull instead, or run
eg config branch.$branch.merge BRANCHANME
EOF
exit 1;
}
$self->{merge_branch} = $merge_branch;
}
sub run {
my $self = shift;
my $package_name = ref($self);
# Get value to set ORIG_HEAD to (unless we are on the initial commit)
Util::push_debug(new_value => 0);
my ($retval, $orig_sha1sum) =
ExecUtil::execute_captured("$git_cmd rev-parse HEAD", ignore_ret => 1);
my $has_orig_head = ($retval == 0);
Util::pop_debug();
# Do the fetch && reset, making sure to set ORIG_HEAD
my ($ret, $output) =
ExecUtil::execute_captured("$git_cmd fetch $self->{repository} " .
"$self->{merge_branch}:$self->{local_branch}",
ignore_ret => 1);
if ($output =~ /\[rejected\].*\(non fast forward\)/) {
die "fatal: Cannot update because you have local commits; " .
"try 'eg pull' instead.\n";
} elsif ($ret != 0) {
die "Error updating (output = $output); please report the bug, and\n" .
"try using 'eg pull' instead.\n";
} else {
$ret = ExecUtil::execute_captured("$git_cmd reset --hard " .
"$self->{local_branch}");
if ($has_orig_head && $debug < 2) {
open(ORIG_HEAD, "> $self->{git_dir}/ORIG_HEAD");
print ORIG_HEAD $output;
close(ORIG_HEAD);
}
print "Updated the current branch.\n" if ($debug < 2);
}
return $ret;
}
###########################################################################
# version #
###########################################################################
package version;
@version::ISA = qw(subcommand);
BEGIN {
undef *version::new unless $] < 5.010; # avoid name clashing
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(git_repo_needed => 0, @_);
bless($self, $class);
}
# Override help because we don't want to both definining $command{help}
sub help {
my $self = shift;
$self->{'help'} = "
Usage:
eg version
Description:
Show the current version of eg.
";
open(OUTPUT, ">&STDOUT");
print OUTPUT $self->{'help'};
close(OUTPUT);
exit 0;
}
sub run {
my $self = shift;
print "eg version $version\n" if $debug < 2;
print " >>(We can print the eg version directly)<<\n" if $debug == 2;
return $self->SUPER::run();
}
#*************************************************************************#
#*************************************************************************#
#*************************************************************************#
# UTILITY CLASSES #
#*************************************************************************#
#*************************************************************************#
#*************************************************************************#
###########################################################################
# ExecUtil #
###########################################################################
package ExecUtil;
# _execute_impl is the guts for execute() and execute_captured()
sub _execute_impl {
my ($command, @opts) = @_;
my ($ret, $output);
my %options = ( ignore_ret => 0, capture_output => 0, @opts );
if ($debug) {
print " >>Running: '$command'<<\n";
return $options{capture_output} ? (0, "") : 0 if $debug == 2;
}
#
# Execute the relevant command, in a subdirectory if needed, and capturing
# stdout and stderr if wanted
#
if ($options{capture_output}) {
if ($options{capture_stdout_only}) {
$output = `$command`;
} else {
$output = `$command 2>&1`;
}
$ret = $?;
} elsif (defined $outfh) {
open(OUTPUT, "$command 2>&1 |");
while (<OUTPUT>) {
print $outfh $_;
}
close(OUTPUT);
$ret = $?;
} else {
system($command);
$ret = $?;
}
#
# Determine retval
#
if ($ret != 0) {
if (($? & 127) == 2) {
print STDERR "eg: interrupted\n";
}
elsif ($? & 127) {
print STDERR "eg: received signal ".($? & 127)."\n";
}
else {
$ret = ($ret >> 8);
if (! $options{ignore_ret}) {
print STDERR "eg: failed ($ret)\n" if $debug;
if ($ret >> 8 != 0) {
print STDERR "eg: command ($command) failed\n";
}
elsif ($ret != 0) {
print STDERR "eg: command ($command) died (retval=$ret)\n";
}
}
}
}
return $options{capture_output} ? ($ret, $output) : $ret;
}
# executes a command, capturing its output (both STDOUT and STDERR),
# returning both the return value and the output
sub execute_captured {
my ($command, @options) = @_;
return _execute_impl($command, capture_output => 1, @options);
}
# executes a command, returning its chomped output
sub output {
my ($command, @options) = @_;
my ($ret, $output) = execute_captured($command, @options);
die "Failed executing '$command'!\n" if $ret != 0;
chomp($output);
return $output
}
# executes a command (output not captured), returning its return value
sub execute {
my ($command, @options) = @_;
return _execute_impl($command, @options);
}
###########################################################################
# RepoUtil #
###########################################################################
package RepoUtil;
# current_branch: Get the currently active branch
sub current_branch {
Util::push_debug(new_value => $debug ? 1 : 0);
my ($ret, $output) = ExecUtil::execute_captured("$git_cmd symbolic-ref HEAD",
ignore_ret => 1);
Util::pop_debug();
return undef if $ret != 0;
chomp($output);
$output =~ s#refs/heads/## || die "Current branch ($output) is funky.\n";
return $output;
}
sub git_dir {
my $options = {force => 0, @_}; # Hashref initialized as we're told
if (!$options->{force}) {
return $gitdir if ($gitdir);
}
Util::push_debug(new_value => 0);
my ($ret, $output) =
ExecUtil::execute_captured("$git_cmd rev-parse --git-dir", ignore_ret => 1);
Util::pop_debug();
return undef if $ret != 0;
chomp($output);
return $output;
}
sub get_dirs {
my $options = {force => 0, @_}; # Hashref initialized as we're told
if ($curdir && !$options->{force}) {
return ($curdir, $topdir, $gitdir);
}
Util::push_debug(new_value => 0);
$curdir = main::getcwd();
# Get the toplevel repository directory
$topdir = $curdir;
my ($ret, $rel_dir) =
ExecUtil::execute_captured("$git_cmd rev-parse --show-prefix",
ignore_ret => 1);
chomp($rel_dir);
if ($ret != 0) {
$topdir = undef;
} elsif ($rel_dir) {
$rel_dir =~ s#/$##; # Remove trailing slash
$topdir =~ s#\Q$rel_dir\E$##;
$topdir =~ s#/$##; # Remove trailing slash
}
$gitdir = git_dir(force => $options->{force});
Util::pop_debug();
return ($curdir, $topdir, $gitdir);
}
sub initial_commit {
my @output = `$git_cmd rev-parse --verify -q HEAD`;
return $?;
}
sub valid_ref {
my ($ref) = @_;
my ($ret, $sha1sum) =
ExecUtil::execute_captured("$git_cmd rev-parse --verify -q $ref",
ignore_ret => 1);
return $ret == 0;
}
sub files_modified() {
my @output = `$git_cmd status -a`;
return $? == 0;
}
sub merge_branches {
my $git_dir = RepoUtil::git_dir();
my $active_branch = RepoUtil::current_branch() || 'HEAD';
my @merge_branches =
`cat "$git_dir/MERGE_HEAD" | $git_cmd name-rev --stdin`;
@merge_branches = map { /^[0-9a-f]* \((.*)\)$/ && $1 } @merge_branches;
my @all_merge_branches = ($active_branch, @merge_branches);
return @all_merge_branches;
}
sub get_special_state {
my $git_dir = shift;
my $special_state;
if ( -d "$git_dir/rebase-apply" ) {
if ( -f "$git_dir/rebase-apply/rebasing" ) {
return "REBASE";
} elsif ( -f "$git_dir/rebase-apply/applying" ) {
return "APPLY MAIL";
} else {
return "APPLY MAIL OR REBASE";
}
} elsif ( -f "$git_dir/rebase-merge/interactive" ) {
return "INTERACTIVE REBASE";
} elsif ( -d "$git_dir/rebase-merge" ) {
return "MERGE REBASE";
} elsif ( -f "$git_dir/MERGE_HEAD" ) {
return "MERGE";
} elsif ( -f "$git_dir/BISECT_LOG" ) {
return "BISECT";
}
return $special_state;
}
sub get_config {
my $key = shift;
my ($ret, $output) = ExecUtil::execute_captured("$git_cmd config --get $key",
ignore_ret => 1);
return undef if $ret != 0;
chomp($output);
return $output;
}
sub set_config {
my $key = shift;
my $value = shift;
ExecUtil::execute("$git_cmd config $key \"$value\"");
}
sub unset_config {
my $key = shift;
ExecUtil::execute("$git_cmd config --unset $key", ignore_ret => 1);
}
sub get_only_branch {
my $repository = shift;
my $check_type = shift;
if ($debug == 2) {
print " >>Running: '$git_cmd ls-remote -h $repository'<<\n";
return;
}
# Check if the remote repository has exactly 1 branch...if so, return it,
# otherwise throw an error
my ($quoted_repo) = Util::quote_args("$repository");
my ($ret, $output) =
ExecUtil::execute_captured("$git_cmd ls-remote -h $quoted_repo",
capture_stdout_only => 1, ignore_ret => 1);
die "Could not determine remote branches from repository '$repository'\n"
if $ret != 0;
my @remote_refs = split('\n', $output);
die "'$repository' has no branches to $check_type!\n" if @remote_refs == 0;
my @remote_branches = map { m#[0-9a-f]+.*/(.*)$# && $1 } @remote_refs;
if (@remote_branches > 1) {
if ($check_type && $check_type eq "push") {
print STDERR <<EOF;
Aborting: It is not clear which remote branch to push changes to. Please
retry, specifying which branch(es) you want to push into from your current
EOF
} else {
print STDERR <<EOF;
Aborting: It is not clear which remote branch to pull changes from. Please
retry, specifying which branch(es) you want to be merged into your current
EOF
}
print STDERR <<EOF;
branch. Existing remote branches of
$repository
are
@remote_branches
EOF
exit 1;
}
return $remote_branches[0];
}
sub get_default_push_pull_repository {
my $branch = current_branch();
return undef if !$branch;
my $default_remote = `$git_cmd config --get branch.$branch.remote`;
if ($default_remote) {
chomp($default_remote);
return $default_remote;
}
my @output = `$git_cmd config --get-regexp remote\.origin\.*`;
if (@output) {
return "origin";
} else {
print STDERR <<EOF;
Aborting: No repository specified, and "origin" is not set up as a remote
repository. Please specify a repository or setup "origin" by running
eg remote add origin URL
EOF
}
}
sub print_new_unknowns {
my ($new_unknowns) = @_;
my $num = scalar(@$new_unknowns);
print STDERR "New unknown files";
print STDERR " include" if $num > 5;
print STDERR ":\n";
my $i = 0;
foreach my $file (@$new_unknowns) {
print STDERR " $file\n";
last if (++$i >= 5);
}
if ($num > 5) {
print STDERR "Run 'eg status' to see a full list of new unknown files.\n";
}
exit 1;
}
# Error messages spewed by commit with non-clean working copies
sub commit_error_message_checks {
my ($commit_type, $check_for, $status, $new_unknown) = @_;
if ($status->{has_unmerged_changes}) {
print STDERR <<EOF;
Aborting: You have unresolved conflicts from your merge (run 'eg status' to get
the list of files with conflicts). You must first resolve any conflicts and
then mark the relevant files as being ready for commit (see 'eg help stage' to
learn how to do so) before proceeding.
EOF
exit 1;
}
if ($check_for->{no_changes} && $status->{has_no_changes}) {
print STDERR <<EOF;
Committing changes should (usually) only be done on a branch. Specify
--bypass-no-branch-check to commit anyway.
EOF
exit 1;
}
if ($check_for->{no_changes} && $status->{has_no_changes}) {
print STDERR "Aborting: Nothing to commit (run 'eg status' for details).\n";
exit 1;
}
elsif ($check_for->{unknown} && $check_for->{partially_staged} &&
$status->{has_new_unknown_files} &&
$status->{has_unstaged_changes} && $status->{has_staged_changes}) {
print STDERR <<EOF;
Aborting: It is not clear which changes should be committed; you have new
unknown files, staged (explictly marked as ready for commit) changes, and
unstaged changes all present. Run 'eg help $commit_type' for details (in
particular, the -b option and either the -a or --staged options).
EOF
print_new_unknowns($new_unknown);
}
elsif ($check_for->{unknown} && $status->{has_new_unknown_files}) {
print STDERR <<EOF;
Aborting: You have new unknown files present and it is not clear whether
they should be committed. Run 'eg help $commit_type' for details (in
particular the -b option).
EOF
print_new_unknowns($new_unknown);
}
elsif ($check_for->{partially_staged} &&
$status->{has_unstaged_changes} && $status->{has_staged_changes}) {
print STDERR <<EOF;
Aborting: It is not clear which changes should be committed; you have both
staged (explictly marked as ready for commit) changes and unstaged changes
present. Run 'eg help $commit_type' for details (in particular, the -a and
--staged options).
EOF
exit 1;
}
}
# Error messages spewed by push, publish for non-clean working copies
sub push_error_message_checks {
my ($clean_check_type, $check_for, $status, $new_unknown) = @_;
if ($status->{has_unmerged_changes}) {
print STDERR <<EOF;
Aborting: You have unresolved conflicts from your merge (run 'eg status' to get
the list of files with conflicts). You should first resolve any conflicts
before trying to $clean_check_type your work elsewhere.
EOF
exit 1;
}
if ($check_for->{unknown} && $check_for->{changes} &&
$status->{has_new_unknown_files} &&
($status->{has_unstaged_changes} || $status->{has_staged_changes})) {
print STDERR <<EOF;
Aborting: You have new unknown files and changed files present. You should
first commit any such changes (and/or use the -b flag to bypass this check)
before trying to $clean_check_type your work elsewhere.
EOF
print_new_unknowns($new_unknown);
}
elsif ($check_for->{unknown} && $status->{has_new_unknown_files}) {
print STDERR <<EOF;
Aborting: You have new unknown files present. You should either commit these
new files before trying to $clean_check_type your work elsewhere, or use the
-b flag to bypass this check.
EOF
print_new_unknowns($new_unknown);
}
elsif ($check_for->{changes} &&
($status->{has_unstaged_changes} || $status->{has_staged_changes})) {
print STDERR <<EOF;
Aborting: You have modified your files since the last commit. You should
first commit any such changes before trying to $clean_check_type your work
elsewhere, or use the -b flag to bypass this check.
EOF
exit 1;
}
}
sub commit_push_checks {
my ($clean_check_type, $check_for) = @_;
my %status;
# Determine some useful directories
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
# Save debug mode, print out commands used up front
if ($debug) {
Util::push_debug(new_value => 0);
if ($clean_check_type) {
print " >>Commands to gather data for pre-$clean_check_type sanity checks:\n";
} else {
print " >>Commands to gather data for sanity checks:\n";
}
print " $git_cmd status\n";
print " $git_cmd ls-files --unmerged\n";
print " $git_cmd symbolic-ref HEAD\n" if $check_for->{no_branch};
print " cd $top_dir && $git_cmd ls-files --exclude-standard --others --directory --no-empty-directory\n";
} else {
Util::push_debug(new_value => 0);
}
#
# Determine which types of changes are present
#
my ($ret, $output) = ExecUtil::execute_captured("$eg_exec status",
ignore_ret => 1);
my @unmerged_files = `$git_cmd ls-files --unmerged`;
$status{has_new_unknown_files} = ($output =~ /^Newly created unknown files:$/m);
$status{has_unstaged_changes} = ($output =~ /^Changed but not updated/m);
$status{has_staged_changes} = ($output =~ /^Changes ready to be commit/m);
$status{has_no_changes} =
($output =~ /^nothing to commit \(working directory clean\)\n\z/m);
$status{has_unmerged_changes} = (scalar @unmerged_files > 0);
$status{output} = $output;
#
# Determine which unknown files are "newly created"
#
my @new_unknown = `(cd "$top_dir" && $git_cmd ls-files --exclude-standard --others --directory --no-empty-directory)`;
chomp(@new_unknown);
if ($check_for->{unknown} && $status{has_new_unknown_files} &&
-f "$git_dir/info/ignored-unknown") {
my @old_unknown_files = `cat "$git_dir/info/ignored-unknown"`;
chomp(@old_unknown_files);
@new_unknown = Util::difference(\@new_unknown, \@old_unknown_files);
$status{has_new_unknown_files} = (scalar(@new_unknown) > 0);
}
@new_unknown =
Util::reroot_paths__from_to_files($top_dir, $cur_dir, @new_unknown);
Util::pop_debug();
if ($check_for->{no_branch}) {
my $rc = system('$git_cmd symbolic-ref -q HEAD >/dev/null');
$status{has_no_branch} = $rc >> 8;
}
return \%status if !defined $clean_check_type;
if ($clean_check_type =~ /commit/) {
commit_error_message_checks($clean_check_type,
$check_for,
\%status,
\@new_unknown);
} elsif ($clean_check_type eq "push" || $clean_check_type eq "publish") {
push_error_message_checks($clean_check_type,
$check_for,
\%status,
\@new_unknown);
} else {
die "Unrecognized clean_check_type: $clean_check_type";
}
return \%status;
}
sub record_ignored_unknowns {
# Determine some useful directories
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
mkdir "$git_dir/info" unless -d "$git_dir/info";
open(OUTPUT, "> $git_dir/info/ignored-unknown");
my @unknown_files = `cd "$top_dir" && $git_cmd ls-files --exclude-standard --others --directory --no-empty-directory`;
foreach my $file (@unknown_files) {
print OUTPUT $file;
}
close(OUTPUT);
}
sub parse_args {
my (@args) = @_;
Util::push_debug(new_value => 0);
my (@opts, @revs, @files);
my $stop_marker_found;
# Get the opts
while (@args) {
my $arg = shift @args;
if ($arg eq "--") {
$stop_marker_found = 1;
last;
}
if ($arg =~ /^-/) {
push(@opts, $arg);
} else {
unshift(@args, $arg);
last;
}
}
# Get the revisions
if (!$stop_marker_found) {
while (@args) {
my $arg = shift @args;
if ($arg eq "--") {
$stop_marker_found = 1;
last;
}
my @revs_to_check = split('\.\.\.?', $arg);
my $found_invalid_ref = 0;
foreach my $ref (@revs_to_check) {
if (!RepoUtil::valid_ref($ref)) {
$found_invalid_ref = 1;
last;
}
}
if ($found_invalid_ref) {
unshift(@args, $arg);
last;
} else {
push(@revs, $arg);
}
}
}
# Get the files
@files = @args;
if (!$stop_marker_found && @files && $files[0] eq "--") {
shift @files;
} else {
# If "--" appears in argument list and not at front, then some bad
# revisions specified by the user showed up in our @files list since
# they didn't validate as existing revisions.
my $i = -1;
foreach my $file (@files) {
if ($file eq "--") {
die "Bad revision(s): @files[0..$i]\n";
}
++$i;
}
}
## FIXME: I should add sanity checking: whether there are too many revs
## specified (or too few), whether any of @revs are also valid filenames,
## and maybe whether all @files refer to valid paths (maybe including
## only allowing files instead of also directories)
Util::pop_debug();
return (\@opts, \@revs, \@files);
}
sub get_revert_info {
my ($revision, @quoted_files) = @_;
my $marker = "";
$marker = "--" if (@quoted_files);
my @newly_added_files;
my @new_since_rev_files;
my @other_files = @quoted_files;
# If this is a merge commit...
my ($cur_dir, $top_dir, $git_dir) = RepoUtil::get_dirs();
my @merge_branches;
if (-f "$git_dir/MERGE_HEAD") {
@merge_branches = `cat "$git_dir/MERGE_HEAD"`;
chomp(@merge_branches);
}
# Define how to get newly added files since a commit, or new files added
# between two commits
my $get_newish_files = sub {
my $ref1 = shift;
my $ref2 = shift;
my (@files, @lines);
if (defined $ref2) {
@lines = `$git_cmd diff-tree -r $ref1 $ref2 $marker @quoted_files`;
} else {
@lines = `$git_cmd diff-index --cached $ref1 $marker @quoted_files`;
}
foreach my $line (@lines) {
# Check for newly added files (not previously tracked but now staged)
if ($line =~ /:000000 [0-9]+ 0{40} [0-9a-f]{40} A\t(.*)/) {
push(@files, $1);
}
}
# git diff-tree and diff-index return files relative to $top_dir, but
# we want filenames relative to $cur_dir
if ($top_dir ne $cur_dir) {
return Util::reroot_paths__from_to_files($top_dir, $cur_dir, @files);
} else {
return @files;
}
};
# Now, get the files added to the index since the "last commit"
@newly_added_files = &$get_newish_files("HEAD");
for my $branch (@merge_branches) {
my @files = &$get_newish_files($branch);
@newly_added_files = Util::intersect(\@newly_added_files, \@files);
}
if (@newly_added_files) {
@newly_added_files = Util::quote_args(@newly_added_files);
@other_files = Util::difference(\@quoted_files, \@newly_added_files);
}
# Now, get the files that exist in the "last commit" but not the specified
# revision.
if ($revision ne "HEAD" || @merge_branches) {
my @branches;
push(@branches, "HEAD");
push(@branches, @merge_branches);
foreach my $branch (@branches) {
my @files = &$get_newish_files($revision, $branch);
@new_since_rev_files = Util::union(\@new_since_rev_files, \@files);
}
if (@new_since_rev_files) {
@new_since_rev_files = Util::quote_args(@new_since_rev_files);
@other_files = Util::difference(\@other_files, \@new_since_rev_files);
}
}
return (\@newly_added_files, \@new_since_rev_files, \@other_files);
}
###########################################################################
# Util #
###########################################################################
package Util;
# Return items in @$lista but not in @$listb
sub difference {
my ($lista, $listb) = @_;
my %count;
foreach my $item (@$lista) { $count{$item}++ };
foreach my $item (@$listb) { $count{$item}-- };
my @ret = grep { $count{$_} == 1 } keys %count;
}
# Return items in both @$lista and in @$listb
sub intersect {
my ($lista, $listb) = @_;
my %original;
my @both = ();
map { $original{$_} = 1 } @$lista;
@both = grep { $original{$_} } @$listb;
return @both;
}
# Return items in either @$lista or @$listb
sub union {
my ($lista, $listb) = @_;
my %either;
map { $either{$_} = 1 } @$lista;
map { $either{$_} = 1 } @$listb;
return keys %either;
}
# Returns whether @$list contains $item
sub contains {
my ($list, $item) = @_;
my $found = 0;
foreach my $elem (@$list) {
if ($item eq $elem) {
$found = 1;
last;
}
}
return $found;
}
sub uniquify_list {
my @list = @_;
my %unique;
@unique{@list} = @list;
return keys %unique;
}
sub quote_args {
my @args = @_;
# Quote arguments with special characters so that when we
# do something like
# system("$command hardcoded_arg1 @args")
# that the @args will get passed correctly to the shell command $command
my @newargs;
foreach my $arg (@args) {
my $quotes_needed = 0;
if (!$arg || $arg =~ /[;'"<>()\[\]|`* \n\$\\]/) {
$quotes_needed = 1;
}
$arg =~ s#\\#\\\\#g; # Backslash escape backslashes
$arg =~ s#"#\\"#g; # Backslash escape quotes
$arg =~ s#`#\\`#g; # Backslash escape backticks
$arg =~ s#\$#\\\$#g; # Backslash escape dollar signs
$arg = '"'.$arg.'"' if $quotes_needed;
push(@newargs, $arg);
}
return @newargs;
}
# Have git's rev-parse command parse @args and decide which part is files,
# which is options, and which are revisions. Further, have git translate
# revisions into full 40-character hexadecimal commit ids.
sub git_rev_parse {
my @args = @_;
Util::push_debug(new_value => 0);
my @quoted_args = Util::quote_args(@args);
my ($ret, $output) =
ExecUtil::execute_captured("$git_cmd rev-parse @quoted_args",
ignore_ret => 1);
if ($ret != 0) {
$output =~ /^(fatal:.*)$/m && print STDERR "$1\n";
$output =~ /^(Use '--'.*)$/m && print STDERR "$1\n";
exit 1;
}
my @opts =
split('\n', `$git_cmd rev-parse --no-revs --flags @quoted_args`);
my @revs =
split('\n', `$git_cmd rev-parse --revs-only @quoted_args`);
my @files =
split('\n', `$git_cmd rev-parse --no-revs --no-flags @quoted_args`);
# Translate sha1sums back to human specified version of revisions. Note that
# something like "REV1...REV2" is translated into "SHA1 SHA2 ^SHA3", so one
# argument may have become 3 revisions. options and files should translate
# one to one, though, so we can back out the original revision names.
@revs = @args[scalar(@opts)..scalar(@args)-scalar(@files)-1];
Util::pop_debug();
return (\@opts, \@revs, \@files);
}
# reroot_paths__from_to_files
# Given
# $from absolute path of directory files were originally relative to
# $to absolute path of directory you want files relative to
# @files list of files with paths relative to $from
# returns a list of files with paths relative to $to
# For example:
# reroot_paths__from_to_files("/home", "/home/newren", ('bar', '../foo'))
# would return
# ('../bar', '../../foo')
# Another example:
# reroot_paths__from_to_files("/tmp/junk", "/tmp", ('bar', '../foo'))
# would return
# ('junk/bar', 'foo')
sub reroot_paths__from_to_files {
my ($from, $to, @files) = @_;
$from =~ s#/*$#/#; # Make sure $from ends with exactly 1 slash
$to =~ s#/*$#/#; # Make sure $to ends with exactly 1 slash
my @new_paths;
foreach my $file (@files) {
# Get the old path for the file, removing any "PATH/.." sequences
my $oldpath = "$from$file";
$oldpath =~ s#/+#/#; # Remove duplicate slashes in path
$oldpath = "$1$2" while $oldpath =~ m#^(.*?)(?!\.\./)[^/]+/\.\./(.*)$#;
# Find what $oldpath and $to have in common
my $common_leading_path = "";
my $combined = "$oldpath\n$to";
if ($combined =~ /^(.*).*\n\1.*$/) {
$common_leading_path = $1;
}
# Now get the unique parts of $oldpath and $to
my $remainder_old_path = substr($oldpath, length($common_leading_path));
my $remainder_to = substr($to, length($common_leading_path));
# Do an s/DIRECTORY_NAME/../ on remainder_to, since we want to know
# the relative path for getting from $to to $from.
$remainder_to =~ s#[^/]+#..#g;
push(@new_paths, "$remainder_to$remainder_old_path");
}
return @new_paths;
}
{
my @debug_values;
sub push_debug {
my @opts = @_;
my %options = ( @opts );
die "Called without new_value!" if !defined($options{new_value});
my $old_value = $debug;
push(@debug_values, $debug);
$debug = $options{new_value};
return $old_value;
}
sub pop_debug {
$debug = pop @debug_values;
}
}
#*************************************************************************#
#*************************************************************************#
#*************************************************************************#
# MAIN PROGRAM #
#*************************************************************************#
#*************************************************************************#
#*************************************************************************#
package main;
sub launch {
my $job=shift;
my $orig_job = $job;
$job =~ s/-/_/; # Packages must have underscores, commands often have dashes
# Create the action to execute
my $action;
$action = $job->new() if $job->can("new");
$action = subcommand->new(command => $orig_job) if !$job->can("new");
my $ret;
# preprocess
if ($action->can("preprocess")) {
# Do not skip commands normally executed during the preprocess stage,
# since they just gather data.
Util::push_debug(new_value => $debug ? 1 : 0);
print ">>Stage: Preprocess<<\n" if $debug;
$action->preprocess();
Util::pop_debug();
}
# run & postprocess
if (!$action->can("postprocess")) {
print ">>Stage: Run<<\n" if $debug;
$ret = $action->run();
} else {
my $output = "";
open($outfh, '>', \$output) || die "eg $job: cannot open \$outfh: $!";
print ">>Stage: Run<<\n" if $debug;
$ret = $action->run();
print ">>Stage: Postprocess<<\n" if $debug;
$action->postprocess($output);
}
# wrapup
if ($action->can("wrapup")) {
print ">>Stage: Wrapup<<\n" if $debug;
$action->wrapup();
}
exit $ret;
}
sub version {
my $version_obj = "version"->new();
$version_obj->run();
exit 0;
}
# User gave invalid input; print an error_message, then show command usage
sub help {
my $error_message = shift;
my %extra_args;
# Clear out any arguments so that help object doesn't think we asked for
# a specific help topic.
@ARGV = ();
# Print any error message we were given
if (defined $error_message) {
print STDERR "$error_message\n\n";
$extra_args{exit_status} = 1;
}
# Now show help.
my $help_obj = "help"->new(%extra_args);
$help_obj->run();
}
sub main {
#
# Get any global options
#
Getopt::Long::Configure("no_bundling", "no_permute",
"pass_through", "no_auto_abbrev", "no_ignore_case");
my $record_arg = sub { $git_cmd .= " --$_[0]"; };
my $record_args = sub { $git_cmd .= " --$_[0]=$_[1]"; };
my $result=GetOptions(
"--debug" => sub { $debug = 1 },
"--help" => sub { help() },
"--translate" => sub { $debug = 2 },
"--version" => sub { version() },
"exec-path=s" => sub { &$record_args(@_) },
"paginate|p" => sub { $use_pager = 1; &$record_arg(@_) },
"no-pager" => sub { $use_pager = 0; &$record_arg(@_) },
"bare" => sub { &$record_arg(@_) },
"git-dir=s" => sub { &$record_args(@_) },
"work-tree=s" => sub { &$record_args(@_) },
);
# Make sure all global args are passed to eg subprocesses as well...
$eg_exec .= substr($git_cmd, index($git_cmd, ' ')) if $git_cmd ne "git";
# Sanity check the arguments
die "eg: Error parsing arguments. (Try 'eg help')\n" if !$result;
die "eg: No subcommand specified. (Try 'eg help')\n" if @ARGV < 1;
die "eg: Invalid argument '$ARGV[0]'. (Try 'eg help')\n"
if ($ARGV[0] !~ m#^[a-z]#);
#
# Fix the environment, if needed
#
if (defined $ENV{"EG_GIT_TEST_PATH"}) {
# Trim the first dir out of path, since it has a symlink git -> eg
my @oldpaths = split(/:/, $ENV{PATH});
my @newpaths = ();
foreach my $path (@oldpaths) {
if (! -l "$path/git" || readlink("$path/git") !~ m#/eg$# ) {
push(@newpaths, $path);
}
}
$ENV{PATH} = join(':', @newpaths);
}
#
# Now execute the action
#
my $action = shift @ARGV;
launch($action);
}
main();
|