Add name field to external configuration

The fetch_externals.py script does not detect if the 'fetchurl' field
changes in the 'externals.json' file. To handle this an external
repo can be assigeded a 'name' field, which allows the remote to be
updated.

Change-Id: I175f7811929baafad84e0c46c7cdd273b91549b1
diff --git a/externals.json b/externals.json
index 67adc9a..ad9e62c 100644
--- a/externals.json
+++ b/externals.json
@@ -41,8 +41,9 @@
         },
         {
             "path": "core_software/rtos/threadx",
-            "fetchurl": "https://github.com/azure-rtos/threadx",
-            "revision": "master"
+            "name": "linaro",
+            "fetchurl": "https://github.com/Linaro/threadx",
+            "revision": "mps3_an547"
         },
         {
             "path": "core_software/tflite_micro",
diff --git a/fetch_externals.py b/fetch_externals.py
index 3cd6620..8121808 100755
--- a/fetch_externals.py
+++ b/fetch_externals.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 #
-# Copyright (c) 2019-2020 Arm Limited. All rights reserved.
+# Copyright (c) 2019-2020,2022 Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -45,14 +45,21 @@
 ###############################################################################
 
 class Git(object):
-    def __init__(self, pwd, path, fetchurl, pushurl=None, revision='origin/master'):
+    def __init__(self, pwd, path, name, fetchurl, pushurl=None, revision='origin/master'):
         self.pwd = pwd
         self.path = path
+        self.name = name
         self.absolutepath = os.path.join(pwd, path)
         self.fetchurl = fetchurl
         self.pushurl = pushurl
         self.revision = revision
 
+    def checkout_and_update(self):
+        self.init()
+        self.remote_add(self.name, self.revision, self.fetchurl, self.pushurl)
+        self.fetch(self.name)
+        self.checkout(self.name, self.revision)
+
     def init(self):
         if not os.path.exists(self.absolutepath):
             os.makedirs(self.absolutepath)
@@ -60,55 +67,61 @@
         if not os.path.exists(os.path.join(self.absolutepath, '.git')):
             check_output(['git', 'init'], cwd=self.absolutepath)
 
-    def remote_add(self, name, fetchurl):
-        remotes = check_output(['git', 'remote'], cwd=self.absolutepath).decode('utf-8').split('\n')
-        if not name in remotes:
-            check_output(['git', 'remote', 'add', '-m', self.revision, name, self.fetchurl], cwd=self.absolutepath)
+    def remote_add(self, name, revision, fetchurl, pushurl):
+        remotes = self.__get_remotes()
 
-            if self.pushurl:
-                check_output(['git', 'remote', 'set-url', '--add', '--push', name, self.pushurl], cwd=self.absolutepath)
+        if name in remotes:
+            if fetchurl != remotes[name]['fetch']:
+                raise Exception("Fetch url '%s' from repository for remote '%s' does not match fetch url '%s' from manifest." % (fetchurl, name, remotes[name]['fetch']))
 
-    def fetch(self):
-        check_output(['git', 'fetch'], cwd=self.absolutepath)
+            if pushurl not in (None, remotes[name]['push']):
+                raise Exception("Push url '%s' from repository for remote '%s' does not match push url '%s' from manifest." % (pushurl, name, remotes[name]['push']))
+        else:
+            check_output(['git', 'remote', 'add', '-m', revision, name, fetchurl], cwd=self.absolutepath)
 
-    def checkout(self, revision):
-        rev = self.__get_rev(revision)
+            if pushurl:
+                check_output(['git', 'remote', 'set-url', '--add', '--push', name, pushurl], cwd=self.absolutepath)
+
+    def fetch(self, name):
+        check_output(['git', 'fetch', name], cwd=self.absolutepath)
+
+    def checkout(self, name, revision):
+        rev = self.__get_rev(name, revision)
         check_output(['git', 'checkout', rev], stderr=subprocess.STDOUT, cwd=self.absolutepath)
 
-    def clone(self):
-        if not os.path.exists(os.path.join(self.absolutepath, '.git')):
-            self.init()
-            self.remote_add('origin', self.fetchurl)
-            self.fetch()
-            self.checkout(self.revision)
-
-    def rebase(self):
-        rev = self.__get_rev(self.revision)
-        check_output(['git', 'rebase', rev], cwd=self.absolutepath)
-
-    def set_sha1(self):
-        self.revision = self.__get_rev(self.revision)
-
-    def get_dict(self):
+    def get_dict(self, sha1):
         data = {}
         data['path'] = self.path
+        data['name'] = self.name
         data['fetchurl'] = self.fetchurl
 
         if self.pushurl:
             data['pushurl'] = self.pushurl
 
-        data['revision'] = self.revision
+        if sha1:
+            data['revision'] = self.__get_rev(self.name, self.revision)
+        else:
+            data['revision'] = self.revision
 
         return data
 
-    def __get_rev(self, revision):
+    def __get_rev(self, name, revision):
         try:
-            rev = check_output(['git', 'rev-parse', 'origin/' + self.revision], stderr=subprocess.STDOUT, cwd=self.absolutepath)
+            rev = check_output(['git', 'rev-parse', name + '/' + revision], stderr=subprocess.STDOUT, cwd=self.absolutepath)
         except:
-            rev = check_output(['git', 'rev-parse', self.revision], cwd=self.absolutepath)
+            rev = check_output(['git', 'rev-parse', revision], cwd=self.absolutepath)
 
         return rev.decode('utf-8').strip()
 
+    def __get_remotes(self):
+        remotes = {}
+        for remote in check_output(['git', 'remote'], cwd=self.absolutepath).decode('utf-8').strip().split('\n'):
+            fetch = check_output(['git', 'remote', 'get-url', remote], cwd=self.absolutepath).decode('utf-8').strip()
+            push = check_output(['git', 'remote', 'get-url', '--push', remote], cwd=self.absolutepath).decode('utf-8').strip()
+            remotes[remote] = { 'fetch': fetch, 'push': push }
+
+        return remotes
+
 ###############################################################################
 # Externals class
 ###############################################################################
@@ -125,28 +138,22 @@
             data = json.load(fp)
 
         for ext in data['externals']:
-            git = Git(self.pwd, ext['path'], ext['fetchurl'], ext.get('pushurl', None), ext['revision'])
+            git = Git(self.pwd, ext['path'], ext.get('name', 'origin'), ext['fetchurl'], ext.get('pushurl', None), ext['revision'])
             self.externals.append(git)
 
     def fetch(self):
         for ext in self.externals:
-            ext.clone()
-            ext.fetch()
-            ext.checkout(ext.revision)
+            ext.checkout_and_update()
 
-    def set_sha1(self):
-        for ext in self.externals:
-            ext.set_sha1()
-
-    def get_dict(self):
+    def get_dict(self, sha1):
         data = { 'externals': [] }
         for ext in self.externals:
-            data['externals'].append(ext.get_dict())
+            data['externals'].append(ext.get_dict(sha1))
 
         return data
 
-    def dump(self, fhandle):
-        fhandle.write(json.dumps(self.get_dict(), sort_keys=False, separators=(',', ': '), indent=4))
+    def dump(self, fhandle, sha1):
+        fhandle.write(json.dumps(self.get_dict(sha1), sort_keys=False, separators=(',', ': '), indent=4))
 
 ###############################################################################
 # Handle functions
@@ -158,11 +165,7 @@
 
 def handle_dump(args):
     externals = Externals(args.configuration)
-
-    if args.sha1:
-        externals.set_sha1()
-
-    externals.dump(sys.stdout)
+    externals.dump(sys.stdout, args.sha1)
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Fetch external repositories.')