Merge "Add --sync support to push."
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index f49c69d..68ae4af 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -126,8 +126,9 @@
         " reverse --remove-all     remove all reverse socket connections from device\n"
         "\n"
         "file transfer:\n"
-        " push LOCAL... REMOTE\n"
+        " push [--sync] LOCAL... REMOTE\n"
         "     copy local files/directories to device\n"
+        "     --sync: only push files that are newer on the host than the device\n"
         " pull [-a] REMOTE... LOCAL\n"
         "     copy files/dirs from device\n"
         "     -a: preserve file timestamp and mode\n"
@@ -1233,9 +1234,8 @@
     return 0;
 }
 
-static void parse_push_pull_args(const char** arg, int narg,
-                                 std::vector<const char*>* srcs,
-                                 const char** dst, bool* copy_attrs) {
+static void parse_push_pull_args(const char** arg, int narg, std::vector<const char*>* srcs,
+                                 const char** dst, bool* copy_attrs, bool* sync) {
     *copy_attrs = false;
 
     srcs->clear();
@@ -1248,6 +1248,10 @@
                 // Silently ignore for backwards compatibility.
             } else if (!strcmp(*arg, "-a")) {
                 *copy_attrs = true;
+            } else if (!strcmp(*arg, "--sync")) {
+                if (sync != nullptr) {
+                    *sync = true;
+                }
             } else if (!strcmp(*arg, "--")) {
                 ignore_flags = true;
             } else {
@@ -1654,19 +1658,20 @@
     }
     else if (!strcmp(argv[0], "push")) {
         bool copy_attrs = false;
+        bool sync = false;
         std::vector<const char*> srcs;
         const char* dst = nullptr;
 
-        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs);
+        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync);
         if (srcs.empty() || !dst) return syntax_error("push requires an argument");
-        return do_sync_push(srcs, dst) ? 0 : 1;
+        return do_sync_push(srcs, dst, sync) ? 0 : 1;
     }
     else if (!strcmp(argv[0], "pull")) {
         bool copy_attrs = false;
         std::vector<const char*> srcs;
         const char* dst = ".";
 
-        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs);
+        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr);
         if (srcs.empty()) return syntax_error("pull requires an argument");
         return do_sync_pull(srcs, dst, copy_attrs) ? 0 : 1;
     }
@@ -2086,7 +2091,7 @@
     std::vector<const char*> apk_file = {argv[last_apk]};
     std::string apk_dest = android::base::StringPrintf(
         where, android::base::Basename(argv[last_apk]).c_str());
-    if (!do_sync_push(apk_file, apk_dest.c_str())) goto cleanup_apk;
+    if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk;
     argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */
     result = pm_command(transport, serial, argc, argv);
 
diff --git a/adb/file_sync_client.cpp b/adb/file_sync_client.cpp
index 22bd2f2..2576fb1 100644
--- a/adb/file_sync_client.cpp
+++ b/adb/file_sync_client.cpp
@@ -674,11 +674,22 @@
     return true;
 }
 
-static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath,
-                      unsigned mtime, mode_t mode)
-{
+static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath, unsigned mtime,
+                      mode_t mode, bool sync) {
     std::string path_and_mode = android::base::StringPrintf("%s,%d", rpath, mode);
 
+    if (sync) {
+        struct stat st;
+        if (sync_lstat(sc, rpath, &st)) {
+            // For links, we cannot update the atime/mtime.
+            if ((S_ISREG(mode & st.st_mode) && st.st_mtime == static_cast<time_t>(mtime)) ||
+                (S_ISLNK(mode & st.st_mode) && st.st_mtime >= static_cast<time_t>(mtime))) {
+                sc.RecordFilesSkipped(1);
+                return true;
+            }
+        }
+    }
+
     if (S_ISLNK(mode)) {
 #if !defined(_WIN32)
         char buf[PATH_MAX];
@@ -902,7 +913,7 @@
             if (list_only) {
                 sc.Println("would push: %s -> %s", ci.lpath.c_str(), ci.rpath.c_str());
             } else {
-                if (!sync_send(sc, ci.lpath.c_str(), ci.rpath.c_str(), ci.time, ci.mode)) {
+                if (!sync_send(sc, ci.lpath.c_str(), ci.rpath.c_str(), ci.time, ci.mode, false)) {
                     return false;
                 }
             }
@@ -916,7 +927,7 @@
     return true;
 }
 
-bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
+bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
@@ -981,7 +992,7 @@
                 dst_dir.append(android::base::Basename(src_path));
             }
 
-            success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(), false, false);
+            success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(), sync, false);
             continue;
         } else if (!should_push_file(st.st_mode)) {
             sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
@@ -1002,7 +1013,7 @@
 
         sc.NewTransfer();
         sc.SetExpectedTotalBytes(st.st_size);
-        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode);
+        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync);
         sc.ReportTransferRate(src_path, TransferDirection::push);
     }
 
diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h
index 90f1965..6606efd 100644
--- a/adb/file_sync_service.h
+++ b/adb/file_sync_service.h
@@ -81,7 +81,7 @@
 
 void file_sync_service(int fd, void* cookie);
 bool do_sync_ls(const char* path);
-bool do_sync_push(const std::vector<const char*>& srcs, const char* dst);
+bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync);
 bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
                   bool copy_attrs, const char* name=nullptr);
 
diff --git a/adb/test_device.py b/adb/test_device.py
index 9ef4676..737d0c2 100644
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -1130,8 +1130,18 @@
             if host_dir is not None:
                 shutil.rmtree(host_dir)
 
+    def verify_sync(self, device, temp_files, device_dir):
+        """Verifies that a list of temp files was synced to the device."""
+        # Confirm that every file on the device mirrors that on the host.
+        for temp_file in temp_files:
+            device_full_path = posixpath.join(
+                device_dir, temp_file.base_name)
+            dev_md5, _ = device.shell(
+                [get_md5_prog(self.device), device_full_path])[0].split()
+            self.assertEqual(temp_file.checksum, dev_md5)
+
     def test_sync(self):
-        """Sync a randomly generated directory of files to specified device."""
+        """Sync a host directory to the data partition."""
 
         try:
             base_dir = tempfile.mkdtemp()
@@ -1141,9 +1151,10 @@
             os.makedirs(full_dir_path)
 
             # Create 32 random files within the host mirror.
-            temp_files = make_random_host_files(in_dir=full_dir_path, num_files=32)
+            temp_files = make_random_host_files(
+                in_dir=full_dir_path, num_files=32)
 
-            # Clean up any trash on the device.
+            # Clean up any stale files on the device.
             device = adb.get_device()  # pylint: disable=no-member
             device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
 
@@ -1155,19 +1166,35 @@
             else:
                 os.environ['ANDROID_PRODUCT_OUT'] = old_product_out
 
-            # Confirm that every file on the device mirrors that on the host.
-            for temp_file in temp_files:
-                device_full_path = posixpath.join(self.DEVICE_TEMP_DIR,
-                                                  temp_file.base_name)
-                dev_md5, _ = device.shell(
-                    [get_md5_prog(self.device), device_full_path])[0].split()
-                self.assertEqual(temp_file.checksum, dev_md5)
+            self.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            #self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
         finally:
             if base_dir is not None:
                 shutil.rmtree(base_dir)
 
+    def test_push_sync(self):
+        """Sync a host directory to a specific path."""
+
+        try:
+            temp_dir = tempfile.mkdtemp()
+            temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
+
+            device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
+
+            # Clean up any stale files on the device.
+            device = adb.get_device()  # pylint: disable=no-member
+            device.shell(['rm', '-rf', device_dir])
+
+            device.push(temp_dir, device_dir, sync=True)
+
+            self.verify_sync(device, temp_files, device_dir)
+
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if temp_dir is not None:
+                shutil.rmtree(temp_dir)
+
     def test_unicode_paths(self):
         """Ensure that we can support non-ASCII paths, even on Windows."""
         name = u'로보카 폴리'