@@ -1509,6 +1509,140 @@ static int maintenance_unregister(void)
15091509 return run_command (& config_unset );
15101510}
15111511
1512+ #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
1513+ #define END_LINE "# END GIT MAINTENANCE SCHEDULE"
1514+
1515+ static void populate_crontab_args (struct strvec * argv , const char * crontab )
1516+ {
1517+ char * name_dup , * word_start , * iter ;
1518+
1519+ if (!crontab ) {
1520+ strvec_push (argv , "crontab" );
1521+ return ;
1522+ }
1523+
1524+ name_dup = xstrdup (crontab );
1525+ word_start = name_dup ;
1526+ iter = name_dup ;
1527+
1528+ while (* iter ) {
1529+ int space = (* iter == ' ' );
1530+ if (space )
1531+ * iter = 0 ;
1532+ iter ++ ;
1533+ if (space || * iter == 0 ) {
1534+ strvec_push (argv , word_start );
1535+ word_start = iter ;
1536+ }
1537+ }
1538+ free (name_dup );
1539+ }
1540+
1541+ static int update_background_schedule (int run_maintenance )
1542+ {
1543+ int result = 0 ;
1544+ int in_old_region = 0 ;
1545+ struct child_process crontab_list = CHILD_PROCESS_INIT ;
1546+ struct child_process crontab_edit = CHILD_PROCESS_INIT ;
1547+ FILE * cron_list , * cron_in ;
1548+ const char * get_test_crontab_name ;
1549+ struct strbuf line = STRBUF_INIT ;
1550+ struct lock_file lk ;
1551+ char * lock_path = xstrfmt ("%s/schedule" , the_repository -> objects -> odb -> path );
1552+
1553+ if (hold_lock_file_for_update (& lk , lock_path , LOCK_NO_DEREF ) < 0 )
1554+ return error (_ ("another process is scheduling background maintenance" ));
1555+
1556+ get_test_crontab_name = getenv ("GIT_TEST_CRONTAB" );
1557+
1558+ populate_crontab_args (& crontab_list .args , get_test_crontab_name );
1559+ strvec_push (& crontab_list .args , "-l" );
1560+ crontab_list .in = -1 ;
1561+ crontab_list .out = dup (lk .tempfile -> fd );
1562+ crontab_list .git_cmd = 0 ;
1563+
1564+ if (start_command (& crontab_list )) {
1565+ result = error (_ ("failed to run 'crontab -l'; your system might not support 'cron'" ));
1566+ goto cleanup ;
1567+ }
1568+
1569+ if (finish_command (& crontab_list )) {
1570+ result = error (_ ("'crontab -l' died" ));
1571+ goto cleanup ;
1572+ }
1573+
1574+ /*
1575+ * Read from the .lock file, filtering out the old
1576+ * schedule while appending the new schedule.
1577+ */
1578+ cron_list = fdopen (lk .tempfile -> fd , "r" );
1579+ rewind (cron_list );
1580+
1581+ populate_crontab_args (& crontab_edit .args , get_test_crontab_name );
1582+ crontab_edit .in = -1 ;
1583+ crontab_edit .git_cmd = 0 ;
1584+
1585+ if (start_command (& crontab_edit )) {
1586+ result = error (_ ("failed to run 'crontab'; your system might not support 'cron'" ));
1587+ goto cleanup ;
1588+ }
1589+
1590+ cron_in = fdopen (crontab_edit .in , "w" );
1591+ if (!cron_in ) {
1592+ result = error (_ ("failed to open stdin of 'crontab'" ));
1593+ goto done_editing ;
1594+ }
1595+
1596+ while (!strbuf_getline_lf (& line , cron_list )) {
1597+ if (!in_old_region && !strcmp (line .buf , BEGIN_LINE ))
1598+ in_old_region = 1 ;
1599+ if (in_old_region )
1600+ continue ;
1601+ fprintf (cron_in , "%s\n" , line .buf );
1602+ if (in_old_region && !strcmp (line .buf , END_LINE ))
1603+ in_old_region = 0 ;
1604+ }
1605+
1606+ if (run_maintenance ) {
1607+ fprintf (cron_in , "\n%s\n" , BEGIN_LINE );
1608+ fprintf (cron_in , "# The following schedule was created by Git\n" );
1609+ fprintf (cron_in , "# Any edits made in this region might be\n" );
1610+ fprintf (cron_in , "# replaced in the future by a Git command.\n\n" );
1611+
1612+ fprintf (cron_in , "0 * * * * git for-each-repo --config=maintenance.repo maintenance run --scheduled\n" );
1613+
1614+ fprintf (cron_in , "\n%s\n" , END_LINE );
1615+ }
1616+
1617+ fflush (cron_in );
1618+ fclose (cron_in );
1619+ close (crontab_edit .in );
1620+
1621+ done_editing :
1622+ if (finish_command (& crontab_edit )) {
1623+ result = error (_ ("'crontab' died" ));
1624+ goto cleanup ;
1625+ }
1626+ fclose (cron_list );
1627+
1628+ cleanup :
1629+ rollback_lock_file (& lk );
1630+ return result ;
1631+ }
1632+
1633+ static int maintenance_start (void )
1634+ {
1635+ if (maintenance_register ())
1636+ warning (_ ("failed to add repo to global config" ));
1637+
1638+ return update_background_schedule (1 );
1639+ }
1640+
1641+ static int maintenance_stop (void )
1642+ {
1643+ return update_background_schedule (0 );
1644+ }
1645+
15121646int cmd_maintenance (int argc , const char * * argv , const char * prefix )
15131647{
15141648 static struct option builtin_maintenance_options [] = {
@@ -1546,6 +1680,10 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)
15461680 return maintenance_register ();
15471681 if (!strcmp (argv [0 ], "run" ))
15481682 return maintenance_run ();
1683+ if (!strcmp (argv [0 ], "start" ))
1684+ return maintenance_start ();
1685+ if (!strcmp (argv [0 ], "stop" ))
1686+ return maintenance_stop ();
15491687 if (!strcmp (argv [0 ], "unregister" ))
15501688 return maintenance_unregister ();
15511689 }
0 commit comments