summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/internal-fs-layout.md5
-rw-r--r--git_config.py87
-rw-r--r--git_trace2_event_log.py20
-rw-r--r--subcmds/sync.py23
-rw-r--r--tests/fixtures/test.gitconfig9
-rw-r--r--tests/test_git_config.py17
6 files changed, 152 insertions, 9 deletions
diff --git a/docs/internal-fs-layout.md b/docs/internal-fs-layout.md
index 8cc3cabd..e3be1731 100644
--- a/docs/internal-fs-layout.md
+++ b/docs/internal-fs-layout.md
@@ -146,7 +146,12 @@ Instead, you should use standard Git workflows like [git worktree] or
146 146
147The `.repo/manifests.git/config` file is used to track settings for the entire 147The `.repo/manifests.git/config` file is used to track settings for the entire
148repo client checkout. 148repo client checkout.
149
149Most settings use the `[repo]` section to avoid conflicts with git. 150Most settings use the `[repo]` section to avoid conflicts with git.
151
152Everything under `[repo.syncstate.*]` is used to keep track of sync details for logging
153purposes.
154
150User controlled settings are initialized when running `repo init`. 155User controlled settings are initialized when running `repo init`.
151 156
152| Setting | `repo init` Option | Use/Meaning | 157| Setting | `repo init` Option | Use/Meaning |
diff --git a/git_config.py b/git_config.py
index 3eaf201c..05d824cb 100644
--- a/git_config.py
+++ b/git_config.py
@@ -13,6 +13,7 @@
13# limitations under the License. 13# limitations under the License.
14 14
15import contextlib 15import contextlib
16import datetime
16import errno 17import errno
17from http.client import HTTPException 18from http.client import HTTPException
18import json 19import json
@@ -30,6 +31,10 @@ from repo_trace import Trace
30from git_command import GitCommand 31from git_command import GitCommand
31from git_refs import R_CHANGES, R_HEADS, R_TAGS 32from git_refs import R_CHANGES, R_HEADS, R_TAGS
32 33
34# Prefix that is prepended to all the keys of SyncAnalysisState's data
35# that is saved in the config.
36SYNC_STATE_PREFIX = 'repo.syncstate.'
37
33ID_RE = re.compile(r'^[0-9a-f]{40}$') 38ID_RE = re.compile(r'^[0-9a-f]{40}$')
34 39
35REVIEW_CACHE = dict() 40REVIEW_CACHE = dict()
@@ -262,6 +267,22 @@ class GitConfig(object):
262 self._branches[b.name] = b 267 self._branches[b.name] = b
263 return b 268 return b
264 269
270 def GetSyncAnalysisStateData(self):
271 """Returns data to be logged for the analysis of sync performance."""
272 return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
273
274 def UpdateSyncAnalysisState(self, options, superproject_logging_data):
275 """Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
276
277 Args:
278 options: Options passed to sync returned from optparse. See _Options().
279 superproject_logging_data: A dictionary of superproject data that is to be logged.
280
281 Returns:
282 SyncAnalysisState object.
283 """
284 return SyncAnalysisState(self, options, superproject_logging_data)
285
265 def GetSubSections(self, section): 286 def GetSubSections(self, section):
266 """List all subsection names matching $section.*.* 287 """List all subsection names matching $section.*.*
267 """ 288 """
@@ -717,3 +738,69 @@ class Branch(object):
717 def _Get(self, key, all_keys=False): 738 def _Get(self, key, all_keys=False):
718 key = 'branch.%s.%s' % (self.name, key) 739 key = 'branch.%s.%s' % (self.name, key)
719 return self._config.GetString(key, all_keys=all_keys) 740 return self._config.GetString(key, all_keys=all_keys)
741
742
743class SyncAnalysisState:
744 """Configuration options related to logging of sync state for analysis.
745
746 This object is versioned.
747 """
748 def __init__(self, config, options, superproject_logging_data):
749 """Initializes SyncAnalysisState.
750
751 Saves the following data into the |config| object.
752 - sys.argv, options, superproject's logging data.
753 - repo.*, branch.* and remote.* parameters from config object.
754 - Current time as synctime.
755 - Version number of the object.
756
757 All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
758
759 Args:
760 config: GitConfig object to store all options.
761 options: Options passed to sync returned from optparse. See _Options().
762 superproject_logging_data: A dictionary of superproject data that is to be logged.
763 """
764 self._config = config
765 now = datetime.datetime.utcnow()
766 self._Set('main.synctime', now.isoformat() + 'Z')
767 self._Set('main.version', '1')
768 self._Set('sys.argv', sys.argv)
769 for key, value in superproject_logging_data.items():
770 self._Set(f'superproject.{key}', value)
771 for key, value in options.__dict__.items():
772 self._Set(f'options.{key}', value)
773 config_items = config.DumpConfigDict().items()
774 EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
775 self._SetDictionary({k: v for k, v in config_items
776 if not k.startswith(SYNC_STATE_PREFIX) and
777 k.split('.', 1)[0] in EXTRACT_NAMESPACES})
778
779 def _SetDictionary(self, data):
780 """Save all key/value pairs of |data| dictionary.
781
782 Args:
783 data: A dictionary whose key/value are to be saved.
784 """
785 for key, value in data.items():
786 self._Set(key, value)
787
788 def _Set(self, key, value):
789 """Set the |value| for a |key| in the |_config| member.
790
791 |key| is prepended with the value of SYNC_STATE_PREFIX constant.
792
793 Args:
794 key: Name of the key.
795 value: |value| could be of any type. If it is 'bool', it will be saved
796 as a Boolean and for all other types, it will be saved as a String.
797 """
798 if value is None:
799 return
800 sync_key = f'{SYNC_STATE_PREFIX}{key}'
801 if isinstance(value, str):
802 self._config.SetString(sync_key, value)
803 elif isinstance(value, bool):
804 self._config.SetBoolean(sync_key, value)
805 else:
806 self._config.SetString(sync_key, str(value))
diff --git a/git_trace2_event_log.py b/git_trace2_event_log.py
index fae3d4c8..9c9e5a70 100644
--- a/git_trace2_event_log.py
+++ b/git_trace2_event_log.py
@@ -144,6 +144,19 @@ class EventLog(object):
144 command_event['subcommands'] = subcommands 144 command_event['subcommands'] = subcommands
145 self._log.append(command_event) 145 self._log.append(command_event)
146 146
147 def LogConfigEvents(self, config, event_dict_name):
148 """Append a |event_dict_name| event for each config key in |config|.
149
150 Args:
151 config: Configuration dictionary.
152 event_dict_name: Name of the event dictionary for items to be logged under.
153 """
154 for param, value in config.items():
155 event = self._CreateEventDict(event_dict_name)
156 event['param'] = param
157 event['value'] = value
158 self._log.append(event)
159
147 def DefParamRepoEvents(self, config): 160 def DefParamRepoEvents(self, config):
148 """Append a 'def_param' event for each repo.* config key to the current log. 161 """Append a 'def_param' event for each repo.* config key to the current log.
149 162
@@ -152,12 +165,7 @@ class EventLog(object):
152 """ 165 """
153 # Only output the repo.* config parameters. 166 # Only output the repo.* config parameters.
154 repo_config = {k: v for k, v in config.items() if k.startswith('repo.')} 167 repo_config = {k: v for k, v in config.items() if k.startswith('repo.')}
155 168 self.LogConfigEvents(repo_config, 'def_param')
156 for param, value in repo_config.items():
157 def_param_event = self._CreateEventDict('def_param')
158 def_param_event['param'] = param
159 def_param_event['value'] = value
160 self._log.append(def_param_event)
161 169
162 def ErrorEvent(self, msg, fmt): 170 def ErrorEvent(self, msg, fmt):
163 """Append a 'error' event to the current log.""" 171 """Append a 'error' event to the current log."""
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 74617544..ed656b8c 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -282,7 +282,7 @@ later is required to fix a server side protocol bug.
282 """Returns True if current-branch or use-superproject options are enabled.""" 282 """Returns True if current-branch or use-superproject options are enabled."""
283 return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest) 283 return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest)
284 284
285 def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests): 285 def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data):
286 """Update revisionId of every project with the SHA from superproject. 286 """Update revisionId of every project with the SHA from superproject.
287 287
288 This function updates each project's revisionId with SHA from superproject. 288 This function updates each project's revisionId with SHA from superproject.
@@ -293,6 +293,7 @@ later is required to fix a server side protocol bug.
293 args: Arguments to pass to GetProjects. See the GetProjects 293 args: Arguments to pass to GetProjects. See the GetProjects
294 docstring for details. 294 docstring for details.
295 load_local_manifests: Whether to load local manifests. 295 load_local_manifests: Whether to load local manifests.
296 superproject_logging_data: A dictionary of superproject data that is to be logged.
296 297
297 Returns: 298 Returns:
298 Returns path to the overriding manifest file instead of None. 299 Returns path to the overriding manifest file instead of None.
@@ -312,6 +313,7 @@ later is required to fix a server side protocol bug.
312 submodules_ok=opt.fetch_submodules) 313 submodules_ok=opt.fetch_submodules)
313 update_result = superproject.UpdateProjectsRevisionId(all_projects) 314 update_result = superproject.UpdateProjectsRevisionId(all_projects)
314 manifest_path = update_result.manifest_path 315 manifest_path = update_result.manifest_path
316 superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
315 if manifest_path: 317 if manifest_path:
316 self._ReloadManifest(manifest_path, load_local_manifests) 318 self._ReloadManifest(manifest_path, load_local_manifests)
317 else: 319 else:
@@ -964,8 +966,14 @@ later is required to fix a server side protocol bug.
964 self._UpdateManifestProject(opt, mp, manifest_name) 966 self._UpdateManifestProject(opt, mp, manifest_name)
965 967
966 load_local_manifests = not self.manifest.HasLocalManifests 968 load_local_manifests = not self.manifest.HasLocalManifests
967 if git_superproject.UseSuperproject(opt, self.manifest): 969 use_superproject = git_superproject.UseSuperproject(opt, self.manifest)
968 manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests) or opt.manifest_name 970 superproject_logging_data = {
971 'superproject': use_superproject,
972 'haslocalmanifests': bool(self.manifest.HasLocalManifests),
973 }
974 if use_superproject:
975 manifest_name = self._UpdateProjectsRevisionId(
976 opt, args, load_local_manifests, superproject_logging_data) or opt.manifest_name
969 977
970 if self.gitc_manifest: 978 if self.gitc_manifest:
971 gitc_manifest_projects = self.GetProjects(args, 979 gitc_manifest_projects = self.GetProjects(args,
@@ -1079,6 +1087,15 @@ later is required to fix a server side protocol bug.
1079 file=sys.stderr) 1087 file=sys.stderr)
1080 sys.exit(1) 1088 sys.exit(1)
1081 1089
1090 # Log the previous sync analysis state from the config.
1091 self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
1092 'previous_sync_state')
1093
1094 # Update and log with the new sync analysis state.
1095 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1096 self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
1097 'current_sync_state')
1098
1082 if not opt.quiet: 1099 if not opt.quiet:
1083 print('repo sync has finished successfully.') 1100 print('repo sync has finished successfully.')
1084 1101
diff --git a/tests/fixtures/test.gitconfig b/tests/fixtures/test.gitconfig
index 9b3f2574..e3f51db3 100644
--- a/tests/fixtures/test.gitconfig
+++ b/tests/fixtures/test.gitconfig
@@ -11,3 +11,12 @@
11 intk = 10k 11 intk = 10k
12 intm = 10m 12 intm = 10m
13 intg = 10g 13 intg = 10g
14[repo "syncstate.main"]
15 synctime = 2021-07-29T19:18:53.201328Z
16 version = 1
17[repo "syncstate.sys"]
18 argv = ['/usr/bin/pytest-3']
19[repo "syncstate.superproject"]
20 test = false
21[repo "syncstate.options"]
22 verbose = true
diff --git a/tests/test_git_config.py b/tests/test_git_config.py
index 3300c12f..44ff5974 100644
--- a/tests/test_git_config.py
+++ b/tests/test_git_config.py
@@ -104,6 +104,23 @@ class GitConfigReadOnlyTests(unittest.TestCase):
104 for key, value in TESTS: 104 for key, value in TESTS:
105 self.assertEqual(value, self.config.GetInt('section.%s' % (key,))) 105 self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
106 106
107 def test_GetSyncAnalysisStateData(self):
108 """Test config entries with a sync state analysis data."""
109 superproject_logging_data = {}
110 superproject_logging_data['test'] = False
111 options = type('options', (object,), {})()
112 options.verbose = 'true'
113 TESTS = (
114 ('superproject.test', 'false'),
115 ('options.verbose', 'true'),
116 ('main.version', '1'),
117 )
118 self.config.UpdateSyncAnalysisState(options, superproject_logging_data)
119 sync_data = self.config.GetSyncAnalysisStateData()
120 for key, value in TESTS:
121 self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value)
122 self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime'])
123
107 124
108class GitConfigReadWriteTests(unittest.TestCase): 125class GitConfigReadWriteTests(unittest.TestCase):
109 """Read/write tests of the GitConfig class.""" 126 """Read/write tests of the GitConfig class."""