Merge branch 'bug-fixes-1.7' into 'master'

Bug fixes 1.7

Closes #1678 and #1757

See merge request fdroid/fdroidclient!820
This commit is contained in:
Hans-Christoph Steiner 2019-05-10 18:56:47 +00:00
commit fac36457ea
21 changed files with 290 additions and 63 deletions

View File

@ -42,6 +42,7 @@
android:id="@+id/find_people_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxEms="16"
android:text="@string/nearby_splash__find_people_button"
style="@style/DetailsSecondaryButtonStyle"
app:layout_constraintTop_toBottomOf="@+id/title"
@ -85,6 +86,7 @@
android:id="@+id/request_read_external_storage_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxEms="16"
android:text="@string/nearby_splash__request_permission"
style="@style/DetailsSecondaryButtonStyle"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -37,6 +37,9 @@
android:layout_height="wrap_content"
android:backgroundTint="@color/swap_light_blue"
android:textColor="@android:color/white"
android:maxEms="10"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/menu_install"
tools:ignore="UnusedAttribute" />

View File

@ -28,13 +28,11 @@ public class DeleteCacheService extends JobIntentService {
Log.w(TAG, "Deleting all cached contents!");
try {
File cacheDir = getCacheDir();
if (cacheDir != null) {
FileUtils.deleteDirectory(cacheDir);
}
for (File dir : ContextCompat.getExternalCacheDirs(this)) {
FileUtils.deleteDirectory(dir);
}
} catch (Exception e) {
} catch (Throwable e) { // NOPMD
// ignored
}
}

View File

@ -414,7 +414,7 @@ public class IndexUpdater {
Utils.debugLog(TAG, "Saving new signing certificate in the database for " + repo.address);
ContentValues values = new ContentValues(2);
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), ""));
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
values.put(RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
RepoProvider.Helper.update(context, repo, values);
}

View File

@ -470,7 +470,7 @@ public class IndexV1Updater extends IndexUpdater {
}
Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address);
ContentValues values = new ContentValues(2);
values.put(Schema.RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), ""));
values.put(Schema.RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
values.put(Schema.RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
RepoProvider.Helper.update(context, repo, values);
repo.signingCertificate = certFromJar;

View File

@ -236,7 +236,7 @@ public class UpdateService extends JobIntentService {
Utils.debugLog(TAG, "scheduling update because there is good internet");
schedule(context);
}
} catch (Exception e) {
} catch (Throwable e) { // NOPMD
Utils.debugLog(TAG, e.getMessage());
}
isScheduleIfStillOnWifiRunning = false;

View File

@ -79,6 +79,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@ -96,6 +97,8 @@ public final class Utils {
private static final SimpleDateFormat TIME_FORMAT =
new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.ENGLISH);
private static final TimeZone UTC = TimeZone.getTimeZone("Etc/GMT");
private static final String[] FRIENDLY_SIZE_FORMAT = {
"%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB",
};
@ -384,7 +387,7 @@ public final class Utils {
}
ret = formatter.toString();
formatter.close();
} catch (Exception e) {
} catch (Throwable e) { // NOPMD
Log.w(TAG, "Unable to get certificate fingerprint", e);
}
return ret;
@ -583,6 +586,7 @@ public final class Utils {
}
Date result;
try {
format.setTimeZone(UTC);
result = format.parse(str);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException | ParseException e) {
e.printStackTrace();
@ -595,21 +599,34 @@ public final class Utils {
if (date == null) {
return fallback;
}
format.setTimeZone(UTC);
return format.format(date);
}
/**
* Parses a date string into UTC time
*/
public static Date parseDate(String str, Date fallback) {
return parseDateFormat(DATE_FORMAT, str, fallback);
}
/**
* Formats UTC time into a date string
*/
public static String formatDate(Date date, String fallback) {
return formatDateFormat(DATE_FORMAT, date, fallback);
}
/**
* Parses a date/time string into UTC time
*/
public static Date parseTime(String str, Date fallback) {
return parseDateFormat(TIME_FORMAT, str, fallback);
}
/**
* Formats UTC time into a date/time string
*/
public static String formatTime(Date date, String fallback) {
return formatDateFormat(TIME_FORMAT, date, fallback);
}

View File

@ -165,7 +165,8 @@ public class Repo extends ValueObject {
inuse = cursor.getInt(i) == 1;
break;
case Cols.LAST_UPDATED:
lastUpdated = Utils.parseTime(cursor.getString(i), null);
String dateString = cursor.getString(i);
lastUpdated = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
break;
case Cols.MAX_AGE:
maxage = cursor.getInt(i);
@ -296,7 +297,7 @@ public class Repo extends ValueObject {
if (values.containsKey(Cols.LAST_UPDATED)) {
final String dateString = values.getAsString(Cols.LAST_UPDATED);
lastUpdated = Utils.parseTime(dateString, null);
lastUpdated = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
}
if (values.containsKey(Cols.MAX_AGE)) {

View File

@ -11,7 +11,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.RepoTable;
@ -278,7 +277,8 @@ public class RepoProvider extends FDroidProvider {
if (cursor != null) {
if (cursor.getCount() > 0) {
cursor.moveToFirst();
lastUpdate = Utils.parseDate(cursor.getString(0), null);
String dateString = cursor.getString(0);
lastUpdate = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
}
cursor.close();
}

View File

@ -265,7 +265,7 @@ public class PrivilegedInstaller extends Installer {
PackageManager pm = context.getPackageManager();
try {
pm.getPackageInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
return true;
return pm.getApplicationInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, 0).enabled;
} catch (PackageManager.NameNotFoundException e) {
return false;
}

View File

@ -555,11 +555,16 @@ public class AppDetailsRecyclerViewAdapter
}
updateAntiFeaturesWarning();
buttonSecondaryView.setText(R.string.menu_uninstall);
buttonSecondaryView.setVisibility(app.isUninstallable(context) ? View.VISIBLE : View.INVISIBLE);
buttonSecondaryView.setOnClickListener(onUnInstallClickListener);
buttonPrimaryView.setText(R.string.menu_install);
buttonPrimaryView.setVisibility(versions.size() > 0 ? View.VISIBLE : View.GONE);
buttonSecondaryView.setText(R.string.menu_uninstall);
buttonSecondaryView.setVisibility(app.isUninstallable(context) ? View.VISIBLE : View.INVISIBLE);
buttonSecondaryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.uninstallApk();
}
});
if (callbacks.isAppDownloading()) {
buttonPrimaryView.setText(R.string.downloading);
buttonPrimaryView.setEnabled(false);
@ -568,22 +573,37 @@ public class AppDetailsRecyclerViewAdapter
} else if (!app.isInstalled(context) && suggestedApk != null) {
// Check count > 0 due to incompatible apps resulting in an empty list.
callbacks.disableAndroidBeam();
progressLayout.setVisibility(View.GONE);
// Set Install button and hide second button
buttonPrimaryView.setText(R.string.menu_install);
buttonPrimaryView.setOnClickListener(onInstallClickListener);
buttonPrimaryView.setEnabled(true);
buttonLayout.setVisibility(View.VISIBLE);
progressLayout.setVisibility(View.GONE);
buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.installApk();
}
});
} else if (app.isInstalled(context)) {
callbacks.enableAndroidBeam();
if (app.canAndWantToUpdate(context) && suggestedApk != null) {
buttonPrimaryView.setText(R.string.menu_upgrade);
buttonPrimaryView.setOnClickListener(onUpgradeClickListener);
buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.installApk();
}
});
} else {
Apk mediaApk = app.getMediaApkifInstalled(context);
if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
buttonPrimaryView.setText(R.string.menu_launch);
buttonPrimaryView.setOnClickListener(onLaunchClickListener);
buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.launchApk();
}
});
} else if (!app.isApk && mediaApk != null) {
final File installedFile = new File(mediaApk.getMediaInstallPath(context), mediaApk.apkName);
if (!installedFile.toString().startsWith(context.getApplicationInfo().dataDir)) {
@ -1324,34 +1344,6 @@ public class AppDetailsRecyclerViewAdapter
}
}
private final View.OnClickListener onInstallClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.installApk();
}
};
private final View.OnClickListener onUnInstallClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.uninstallApk();
}
};
private final View.OnClickListener onUpgradeClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.installApk();
}
};
private final View.OnClickListener onLaunchClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.launchApk();
}
};
private boolean uriIsSetAndCanBeOpened(String s) {
if (TextUtils.isEmpty(s)) {
return false;

View File

@ -143,6 +143,9 @@
android:layout_marginTop="5dp"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:maxEms="10"
android:ellipsize="end"
android:singleLine="true"
tools:text="@string/menu_install"/>
<Button

View File

@ -42,6 +42,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:maxEms="10"
android:text="@string/update_all"
style="@style/DetailsPrimaryButtonStyle"
app:layout_constraintEnd_toEndOf="parent"
@ -54,6 +55,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:ellipsize="middle"
android:singleLine="true"
tools:text="Show apps"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -10,6 +10,8 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.io.File;
import java.util.Date;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -192,4 +194,25 @@ public class UtilsTest {
}
// TODO write tests that work with a Certificate
@Test
public void testIndexDatesWithTimeZones() {
for (int h = 0; h < 12; h++) {
for (int m = 0; m < 60; m = m + 15) {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT+%d%02d", h, m)));
String timeString = "2017-11-27_20:13:24";
Date time = Utils.parseTime(timeString, null);
assertEquals("The String representation must match", timeString, Utils.formatTime(time, null));
assertEquals(timeString + " failed to parse", 1511813604000L, time.getTime());
assertEquals("time zones should match", -((h * 60) + m), time.getTimezoneOffset());
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT+%d%02d", h, m)));
String dateString = "2017-11-27";
Date date = Utils.parseDate(dateString, null);
assertEquals("The String representation must match", dateString, Utils.formatDate(date, null));
assertEquals(dateString + " failed to parse", 1511740800000L, date.getTime());
assertEquals("time zones should match", -((h * 60) + m), date.getTimezoneOffset());
}
}
}
}

View File

@ -7,10 +7,12 @@ import android.net.Uri;
import org.fdroid.fdroid.Assert;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
import org.fdroid.fdroid.data.Schema.RepoTable;
import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockRepo;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@ -18,6 +20,7 @@ import org.robolectric.annotation.Config;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import static org.fdroid.fdroid.Assert.assertCantDelete;
import static org.fdroid.fdroid.Assert.assertResultCount;
@ -34,6 +37,13 @@ public class ApkProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL;
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void testAppApks() {
App fdroidApp = insertApp(context, "org.fdroid.fdroid", "F-Droid");
@ -317,10 +327,11 @@ public class ApkProviderTest extends FDroidProviderTest {
apk.antiFeatures = new String[]{"KnownVuln", "Other anti feature"};
apk.features = new String[]{"one", "two", "three"};
long dateTimestamp = System.currentTimeMillis();
apk.added = new Date(dateTimestamp);
apk.hashType = "i'm a hash type";
Date testTime = Utils.parseDate(Utils.formatTime(new Date(System.currentTimeMillis()), null), null);
apk.added = testTime;
ApkProvider.Helper.update(context, apk);
// Should not have inserted anything else, just updated the already existing apk.
@ -340,9 +351,10 @@ public class ApkProviderTest extends FDroidProviderTest {
assertArrayEquals(new String[]{"KnownVuln", "Other anti feature"}, updatedApk.antiFeatures);
assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features);
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
assertEquals(testTime.getYear(), updatedApk.added.getYear());
assertEquals(testTime.getYear(), updatedApk.added.getYear());
assertEquals(testTime.getMonth(), updatedApk.added.getMonth());
assertEquals(testTime.getDay(), updatedApk.added.getDay());
assertEquals("i'm a hash type", updatedApk.hashType);
}

View File

@ -11,6 +11,7 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@ -19,6 +20,7 @@ import org.robolectric.shadows.ShadowContentResolver;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import static org.fdroid.fdroid.Assert.assertContainsOnly;
import static org.fdroid.fdroid.Assert.assertResultCount;
@ -36,6 +38,13 @@ public class AppProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL;
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Before
public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);

View File

@ -30,6 +30,7 @@ import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.RepoTable;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@ -37,6 +38,7 @@ import org.robolectric.annotation.Config;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -48,6 +50,16 @@ public class RepoProviderTest extends FDroidProviderTest {
private static final String[] COLS = RepoTable.Cols.ALL;
/**
* Set to random time zone to make sure that the dates are properly parsed.
*/
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void countEnabledRepos() {
@ -95,7 +107,7 @@ public class RepoProviderTest extends FDroidProviderTest {
private Repo setLastUpdate(Repo repo, Date date) {
ContentValues values = new ContentValues(1);
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatDate(date, null));
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(date, null));
RepoProvider.Helper.update(context, repo, values);
return RepoProvider.Helper.findByAddress(context, repo.address);
}

View File

@ -29,6 +29,7 @@ import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.mock.MockRepo;
import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@ -48,6 +49,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -63,6 +65,16 @@ public class RepoXMLHandlerTest {
private static final String FAKE_SIGNING_CERT = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"; // NOCHECKSTYLE LineLength
/**
* Set to random time zone to make sure that the dates are properly parsed.
*/
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void testExtendedPerms() throws IOException {
Repo expectedRepo = new Repo();
@ -129,6 +141,12 @@ public class RepoXMLHandlerTest {
"org.gege.caldavsyncadapter",
"info.guardianproject.checkey",
});
for (App app : actualDetails.apps) {
if ("org.mozilla.firefox".equals(app.packageName)) {
assertEquals(1411776000000L, app.added.getTime());
assertEquals(1411862400000L, app.lastUpdated.getTime());
}
}
}
@Test(expected = IllegalArgumentException.class)
@ -897,6 +915,10 @@ public class RepoXMLHandlerTest {
List<App> apps = actualDetails.apps;
assertNotNull(apps);
assertEquals(apps.size(), appCount);
for (App app : apps) {
assertTrue("Added should have been set", app.added.getTime() > 0);
assertTrue("Last Updated should have been set", app.lastUpdated.getTime() > 0);
}
List<Apk> apks = actualDetails.apks;
assertNotNull(apks);

File diff suppressed because one or more lines are too long

48
tools/check-string-maxlength.py Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python3
# Remove extra translations
import glob
import os
import sys
import re
from xml.etree import ElementTree
maxlengths = {
"menu_install": 20,
"menu_uninstall": 20,
"nearby_splash__find_people_button": 30,
"nearby_splash__request_permission": 30,
"update_all": 20,
"updates__hide_updateable_apps": 35,
"updates__show_updateable_apps": 35,
}
resdir = os.path.join(os.path.dirname(__file__), '..', 'app', 'src', 'main', 'res')
count = 0
for d in sorted(glob.glob(os.path.join(resdir, 'values-*'))):
locale = d.split('/')[-1][7:]
str_path = os.path.join(d, 'strings.xml')
if not os.path.exists(str_path):
continue
with open(str_path, encoding='utf-8') as fp:
fulltext = fp.read()
tree = ElementTree.parse(str_path)
root = tree.getroot()
for e in root.findall('.//string'):
if maxlengths.get(e.attrib['name']) is not None \
and len(e.text) > maxlengths.get(e.attrib['name']):
print(e.attrib['name'], locale, str(len(e.text)) + ':\t\t"' + e.text + '"')
if count > 0:
print("%d over-long strings found!" % count)
sys.exit(count)

View File

@ -0,0 +1,82 @@
#!/usr/bin/python3
#
# cherry-pick complete translations from weblate
import git
import json
import os
import re
import requests
import sys
def get_paths_tuple(locale):
return (
'metadata/%s/*.txt' % locale,
'metadata/%s/changelogs/*.txt' % locale,
'app/src/main/res/values-%s/strings.xml' % re.sub(r'-', r'-r', locale),
)
projectbasedir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(projectbasedir)
repo = git.Repo(projectbasedir)
weblate = repo.remotes.weblate
weblate.fetch()
upstream = repo.remotes.upstream
upstream.fetch()
url = 'https://hosted.weblate.org/exports/stats/f-droid/f-droid/?format=json'
r = requests.get(url)
r.raise_for_status()
app = r.json()
url = 'https://hosted.weblate.org/exports/stats/f-droid/f-droid-metadata/?format=json'
r = requests.get(url)
r.raise_for_status()
metadata = r.json()
#with open('f-droid-metadata.json') as fp:
# metadata = json.load(fp)
app_locales = dict()
metadata_locales = dict()
merge_locales = []
for locale in app:
app_locales[locale['code']] = locale
for locale in metadata:
metadata_locales[locale['code']] = locale
for locale in sorted(app_locales.keys(), reverse=True):
a = app_locales.get(locale)
m = metadata_locales.get(locale)
if a is not None and a['translated_percent'] == 100 and a['failing'] == 0 \
and m is not None and m['translated_percent'] == 100 and m['failing'] == 0:
print(locale)
merge_locales.append(locale)
if not merge_locales:
sys.exit()
if 'merge_weblate' in repo.heads:
merge_weblate = repo.heads['merge_weblate']
repo.create_tag('previous_merge_weblate', ref=merge_weblate,
message=('Automatically created by %s' % __file__))
else:
merge_weblate = repo.create_head('merge_weblate')
merge_weblate.set_commit(upstream.refs.master)
merge_weblate.checkout()
email_pattern = re.compile(r'by (.*?) <(.*)>$')
for locale in sorted(merge_locales):
commits = list(repo.iter_commits(
str(weblate.refs.master) + '...' + str(upstream.refs.master),
paths=get_paths_tuple(locale), max_count=10))
for commit in reversed(commits):
repo.git.cherry_pick(str(commit))
m = email_pattern.search(commit.summary)
if m:
email = m.group(1) + ' <' + m.group(2) + '>'