Add update --refresh and --keep_date flags

* Use --refresh to update a project with its current version,
  to check for any missing local changes after the update.
* Use --keep_date to not change the last_upgrade_date in METADATA,
  useful to minimize diff output after a refresh update.

Bug: 169725446
Test: .../updater.sh update --refresh --keep_date rust/crates/bitflags
Change-Id: I7e0396c2653eb7b4c859a5887a8b1d62fc446910
diff --git a/archive_utils.py b/archive_utils.py
index 4a10392..b8386aa 100644
--- a/archive_utils.py
+++ b/archive_utils.py
@@ -90,7 +90,7 @@
             return func
     # crates.io download url does not have file suffix
     # e.g., https://crates.io/api/v1/crates/syn/1.0.16/download
-    if url.find('/crates.io/api/') > 0:
+    if url.find('/crates.io/api/') > 0 or url.find('/static.crates.io/crates/'):
         return untar
     return None
 
diff --git a/base_updater.py b/base_updater.py
index d053054..8cf3255 100644
--- a/base_updater.py
+++ b/base_updater.py
@@ -71,3 +71,8 @@
     def latest_url(self) -> metadata_pb2.URL:
         """Gets URL for latest version."""
         return self._new_url
+
+    def use_current_as_latest(self):
+        """Uses current version/url as the latest to refresh project."""
+        self._new_ver = self._old_ver
+        self._new_url = self._old_url
diff --git a/crates_updater.py b/crates_updater.py
index b8e48d3..ba8f16f 100644
--- a/crates_updater.py
+++ b/crates_updater.py
@@ -39,7 +39,7 @@
 class CratesUpdater(Updater):
     """Updater for crates.io packages."""
 
-    dl_path: str
+    download_url: str
     package: str
 
     def is_supported_url(self) -> bool:
@@ -76,7 +76,7 @@
                     self._new_ver, last_id, version, int(v["id"]))):
                 last_id = int(v["id"])
                 self._new_ver = version
-                self.dl_path = v["dl_path"]
+                self.download_url = "https://crates.io" + v["dl_path"]
 
     def check(self) -> None:
         """Checks crates.io and returns whether a new version is available."""
@@ -93,7 +93,13 @@
             url = url + "/" + self._new_ver
             with urllib.request.urlopen(url) as request:
                 data = json.loads(request.read().decode())
-                self.dl_path = data["version"]["dl_path"]
+                self.download_url = "https://crates.io" + data["version"]["dl_path"]
+
+    def use_current_as_latest(self):
+        Updater.use_current_as_latest(self)
+        # A shortcut to use the static download path.
+        self.download_url = "https://static.crates.io/crates/{}/{}-{}.crate".format(
+            self.package, self.package, self._new_ver)
 
     def update(self) -> None:
         """Updates the package.
@@ -101,8 +107,7 @@
         Has to call check() before this function.
         """
         try:
-            url = "https://crates.io" + self.dl_path
-            temporary_dir = archive_utils.download_and_extract(url)
+            temporary_dir = archive_utils.download_and_extract(self.download_url)
             package_dir = archive_utils.find_archive_root(temporary_dir)
             updater_utils.replace_package(package_dir, self._proj_path)
         finally:
diff --git a/external_updater.py b/external_updater.py
index ce0bb3c..7aa5729 100644
--- a/external_updater.py
+++ b/external_updater.py
@@ -17,6 +17,7 @@
 Example usage:
 updater.sh checkall
 updater.sh update kotlinc
+updater.sh update --refresh --keep_date rust/crates/libc
 """
 
 import argparse
@@ -109,7 +110,7 @@
     # For Rust crates, replace GIT url with ARCHIVE url
     if isinstance(updater, CratesUpdater):
         updater.update_metadata(updated_metadata)
-    fileutils.write_metadata(full_path, updated_metadata)
+    fileutils.write_metadata(full_path, updated_metadata, args.keep_date)
     git_utils.add_file(full_path, 'METADATA')
 
     if args.branch_and_commit:
@@ -154,7 +155,10 @@
         else:
             print(color_string(' Up to date.', Color.FRESH))
 
-        if update_lib and (has_new_version or args.force):
+        if update_lib and args.refresh:
+            print('Refreshing the current version')
+            updater.use_current_as_latest()
+        if update_lib and (has_new_version or args.force or args.refresh):
             _do_update(args, updater, metadata)
         return updater
     # pylint: disable=broad-except
@@ -243,6 +247,14 @@
         '--force',
         help='Run update even if there\'s no new version.',
         action='store_true')
+    update_parser.add_argument(
+        '--refresh',
+        help='Run update and refresh to the current version.',
+        action='store_true')
+    update_parser.add_argument(
+        '--keep_date',
+        help='Run update and do not change date in METADATA.',
+        action='store_true')
     update_parser.add_argument('--branch_and_commit',
                                action='store_true',
                                help='Starts a new branch and commit changes.')
diff --git a/fileutils.py b/fileutils.py
index d7dd0fa..f6a51cc 100644
--- a/fileutils.py
+++ b/fileutils.py
@@ -66,7 +66,7 @@
         return text_format.Parse(metadata, metadata_pb2.MetaData())
 
 
-def write_metadata(proj_path: Path, metadata: metadata_pb2.MetaData) -> None:
+def write_metadata(proj_path: Path, metadata: metadata_pb2.MetaData, keep_date: bool) -> None:
     """Writes updated METADATA file for a project.
 
     This function updates last_upgrade_date in metadata and write to the project
@@ -75,13 +75,15 @@
     Args:
       proj_path: Path to the project.
       metadata: The MetaData proto to write.
+      keep_date: Do not change date.
     """
 
-    date = metadata.third_party.last_upgrade_date
-    now = datetime.datetime.now()
-    date.year = now.year
-    date.month = now.month
-    date.day = now.day
+    if not keep_date:
+        date = metadata.third_party.last_upgrade_date
+        now = datetime.datetime.now()
+        date.year = now.year
+        date.month = now.month
+        date.day = now.day
     text_metadata = text_format.MessageToString(metadata)
     with get_metadata_path(proj_path).open('w') as metadata_file:
         metadata_file.write(text_metadata)