вторник, 29 апреля 2014 г.

How to по git. Для склеротиков.

Настройка Git клиента для идентификации пользователя:
git config --global user.name "Your Name"
git config --global user.email "your@email.address"
git config --global core.editor "your editor"
Инициализация репозитория в текущем каталоге:
git init
Создает репозиторий в указанном каталоге:
git init <directory>
Клонирование репозитория на локальную машину:
git clone <repo>
Добавить файл для идексации и следить за всеми изменениями в нем ( можно задавать по маске ):
git add <file>
Добавить каталог для индексации:
git add <directory>
Удалить файл из индексации ( можно задавать по маске ):
git rm <file>
Перенести или переименовать файл / каталог:
git mv <source> <destination>
Сделать "снапшот" всех выполненных изменений:
git commit -a -m "message"
Статус репозитория ( добавление, удаление, изменение файлов ):
git status
Показать историю коммитов:
git log
Показать только определенное количество коммитов:
git log -n <limit>
Показать историю коммитов по конкретному файлу:
git log <file>
Посмотреть различия между последним коммитом и текущими изменениями:
git diff
Просмотр всех различий между коммитами:
git diff <commit> <commit>
Посмотреть различия между коммитом и текущими изменениями:
git diff <commit> <file>
Посмотреть различия между коммитами для файла:
git diff <commit> <commit> <file>
Показать список всех файлов в основной ветке:
git ls-tree master -r --name-only
Работа с ветками

Список веток:
git branch
Создание ветки:
git branch <new_branch_name>
Безопастное удаление ветки, если не были сделаны изменения:
git branch -d <branch_name>
Удаление ветки, даже если были сделаны изменения:
git branch -D <branch_name>
Переименование текущей ветки:
git branch -m <rename_current_branch>
Переход к существующей ветке:
git checkout <branch_name>
Создание и переход к ветке:
git checkout -b <new_branch_name>
Слияние текущей ветки с указанной:
git merge <branch>
Откат изменений

Перейти на последний коммит ветки "master":
git checkout master
Откатить изменения во всех файлах до указанного коммита:
git checkout <commit>
Откатить изменения для конкретного файла до указанного коммита:
git checkout <commit> <file>
Сделать откат всех изменений выполненных в коммите, при этом создается новый коммит указывающий на откат изменений:
git revert <commit>
Отмена изменений до последнего коммита, а также сбрасывает индексацию для конкретного файла:
git reset HEAD <file>
Отмена изменений до последнего коммита и сбрасывает индексацию:
git reset --soft
Отмена изменений до последнего коммита, сбрасывает индексацию, а так же отменить любые изменения в рабочей директории:
git reset --hard
programming Удаляет файлы которые не были добавлены в репозиторий:
git clean -f
Удаляет файлы которые не были добавлены в репозиторий по указанному пути:
git clean -f <path>
Удаляет файлы и каталоги которые не были добавлены в репозиторий:
git clean -df
Только показывает, что будет удаленно:
git clean -n
Работа с удаленными репозиториями

Список соединений с удаленными репозиториями:
git remote
Добавить соединение:
git remote add <name> <url>
Удалить соединение:
git remote rm <name>
Переименовать соединение:
git remote rename <old_name> <new_name>
Получить изменения из репозитория со списком всех веток ( при этом стираются любые локальные изменения ):
git fetch <remote>
Получить изменения из репозитория для конкретной ветки ( при этом стираются любые локальные изменения ):
git fetch <remote> <branch>
Получить копию текущей ветки с удаленного репозитория и слить ее с локальной копией:
git pull <remote>
Залить указанную ветку на удаленный репозиторий:
git push <remote> <branch>


Отдельно про git stash.
Удобно, занимаясь одним делом, «отложить» текущую работу в сторону и отвлечься, скажем, на срочное исправление бага, даже если он находится в другом бранче. После исправления и коммита можно преспокойно вернуться к начатому.
i. hack-hack-hack
ii. git stash
iii. fix-fix-fix
iv. git commit -a -m 'bugfix #31337'
v. git stash pop
Те же, кто знает про git stash, посмотрите на последнюю строку — её отличие от apply в том, что откладываемые результаты не остаются во временном хранилище (посмотрите git stash list после нескольких примений stash!)
Если изменение было фактически законченным, можно оформить коммит не отходят от кассы — git stash save 'commit msg' (взято с http://habrahabr.ru/post/75728/http://habrahabr.ru/post/137408/)
Отдельно про git bisect.
git bisect -  возможность бинарного поиска по коммитам. Для примера приведу следующую ситуацию. В нашем проекте поломался какой-то функционал, в чем конкретно проблема мы понять не можем, но точно знаем, что в прошлом релизе этот функционал точно работал. Так вот команда bisect позволяет нам легко откатить проект на заданное число коммитов и найти тот коммит, в котором неуловимый баг проявляется в первый раз.
Во-первых, запускаем механизм поиска и устанавливаем значение, что текущее состояние проекта нерабочее
git bisect start 
git bisect bad
Далее откатываемся на рабочее состояние проекта — на 10 коммитов назад по истории
git bisect good HEAD~10
Смотрим, проявляется ли этот баг в текущем состоянии. Допустим, сейчас с проектом все хорошо, поэтому продолжаем поиск дальше
git bisect good
Эта команда перематывает состояние проекта в середину, между хорошим коммитом и плохим, т. е. Сейчас мы находимся в пяти коммитах назад по истории. Если баг проявляется, то он находится до текущего коммита, если нет — то после. С каждой такой итерацией расстояние уменьшается в два раза, пока мы не находим плохой коммит (а главное — его автора, к сожалению, это его опять сделали мы).
После завершения поиска сбрасываем репозиторий в исходное состояние
git bisect reset )

пятница, 11 апреля 2014 г.

Используем Apache HttpClient или POST и GET для самых маленьких

Это будет небольшой пост в примерах с минимальным числом комментариев.

Задача: используя httpclient, отправить post - запрос с логином и паролем на некоторый адрес.
Решение: 
HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(SESSIONS_URL); //SESSIONS_URL - наш адрес
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair(StaticInformation.LOGIN,
LOGIN));
nameValuePairs.add(new BasicNameValuePair(
StaticInformation.PASSWORD, PASSWORD));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httpPost, localContext);
int code = response.getStatusLine().getStatusCode(); //code - используем для проверки, прошел ли наш запрос
Пояснение: создаем клиент, создаем запрос, с помощью nameValuePairs забиваем необходимые параметры, делаем запрос, получаем код.

Задача: используя httpclient, отправить пост - запрос с логином и паролем на некоторый адрес при этом сохранить куку для дальнейших post(get) - запросов.
Решение: 
HttpClient httpclient = new DefaultHttpClient();
CookieStore cookieStore = new BasicCookieStore();
HttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
HttpPost httpPost = new HttpPost(StaticInformation.SESSIONS_URL);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair(StaticInformation.LOGIN,
LOGIN));
nameValuePairs.add(new BasicNameValuePair(
StaticInformation.PASSWORD, PASSWORD));

httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httpPost, localContext);
//делаем запрос номер два
HttpPost httpPostOffer = new HttpPost(StaticInformation.NEW_URL);
httpPostOffer.setEntity(<Сюда запишем новую сущность>);
response = httpclient.execute(httpPostOffer, localContext);
code = response.getStatusLine().getStatusCode();
Пояснение: по-сути то же самое, но тут мы должны создать cookieStore - хранилище для наших кук. Создаем и сразу же делаем привязку к контексту(localContext), после этого можно совершать запросы, передавая туда созданный localContext.

Задача: совершить запрос multipart/form-data. Отправить на сервер картинку, закодированную в Base64. Этим же запросом отослать остальную информацию.
Решение:
httpPostOffer.setEntity(complectEntityToPost(offer));

private MultipartEntity complectEntityToPost(Offer offer) {
MultipartEntity entity = new MultipartEntity(
HttpMultipartMode.BROWSER_COMPATIBLE);
// add data for multiform
for (String asset : offer.getAssets()) {
ByteArrayBody body = new ByteArrayBody(Base64.decode(asset,
Base64.DEFAULT), IMAGE_FORMAT,
Converter.currentDateToString()); //добавим данные как на форме
entity.addPart("offer[assets][]", body);
}
// add other data
for (NameValuePair pair : complectPostValuePairs(offer)) {
try {
entity.addPart(pair.getName(), new StringBody(pair.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
return entity;
}

private List<NameValuePair> complectPostValuePairs(Offer offer) {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

nameValuePairs
.add(new BasicNameValuePair("offer[organizationId]", "1"));
nameValuePairs.add(new BasicNameValuePair("offer[cityId]", "16"));
               ...................
}
Пояснение: для  multipart/form-data запросов используется MultipartEntity. С помощью ByteArrayBody можно делать обертки вокруг конструкций типа 
Content-Disposition: form-data; name="offer[assets][]"; filename="icon.png",
передавая необходимые параметры в конструктор. Entity, используемая в предыдущих примерах, лишь часть MultipartEntity, поэтому добавлять параметры можно с помощью метода addPart, где первый параметр - имя параметра, второй параметр - его значение.

Задача: сделать так, чтобы при отправке  multipart/form-data - запроса на сервере корректно отображался русскоязычный текст. 
Решение: 
entity.addPart(pair.getName(), new StringBody(pair.getValue(),
Charset.forName("UTF-8")));
Пояснение: при помощи  StringBody "превращаем" каждую строку (в Java строки Unicode) в UTF - 8. Прежде чем, совершать какие - либо действия с кодировкой, присмотритесь к форме. В моем случае, она выглядит вот так: <form accept-charset='UTF-8' action='/offers' enctype='multipart/form-data' id='offer' method='post'>  

Задача: сделать так, чтобы при отправке обычного post - запроса на сервере корректно отображался русскоязычный текст.
Решение: 
new  UrlEncodedFormEntity(complectPostValuePairs(offer),"UTF-8"));
Пояснение: в качестве параметра для списка из NameValuePair подадим название кодировки.

Задача: сделать гет - запрос.
Пояснение: ничем не отличается от поста, используем HttpGet httpGET. Если нужно куки, юзаем точно тот же способ как и в посте.

Не забываем активно использовать "Проверить элемент" в Google Chrome(нас интересует вкладка "Network"). Просматриваем отданную и переданную информацию.






среда, 9 апреля 2014 г.

Crashlytics как неплохая альтернатива Bugsense

Bugsense - очень и очень неплохой crash reporter для мобильных устройств, существует он довольно давно, и многие мои знакомые пользуются именно им. Не так давно появилась неплохая альтернатива Bugsense, которая еще находится в режиме тестирования, но многие "фишечки" видны уже сейчас. Это crashlytics. Сейчас немного расскажу и покажу как выглядит crashlytics и что это за "фишечки" такие. Во -  первых, crashlytics - это не только вебсайт, но и плагин для eclipse, x-code, android - studio. Плагин этот ставится "Help -> Install new software"(это в eclipse, ничего удивительного, как видите).  Именно в этом и заключается главная фишка - можно видеть баги прямо из вашей среды разработки :) Все очень просто - вы запускаете crashlytics, с помощью кнопки на панели инструментов: 


Затем отмечаете те приложения, за которыми хотите "следить", crashlytics, в свою очередь, добавляет мета - информацию в Manifest - файл вашего Android - проекта:<meta-data android:name="com.crashlytics.ApiKey" android:value="KEY"/> (где KEY - некоторая строка). 
Чтобы все изменения вступили в силу, вам остается только забилдить и запустить ваш проект, а crashlytics подождет:


Всё!!! Теперь вы видите репорты прямо в eclipse! 


Но это еще не все! Как только к вам "пришла" ошибка, вы можете перейти на сайт(который очень крут - фишка номер два :) ), а там просмотреть не только полную информацию о баге, но и:

1) Увидеть красивую статистику (по устройствам, дням, неделям и т.д)
2) Сделать заметку к ошибке
3) Посмотреть состояние всех потоков устройства, на котором был замечен баг
4) Увидеть еще многое и многое :)    


Очень и очень рекомендую.


пятница, 28 марта 2014 г.

Анализируем исходный код 30 apk с помощью PMD

Как — то раз, бродя по просторам интернета, я наткнулся на PMD. PMD служит для поиска «общих недостатков» кода, таких как пустые блоки, создание объекта, который никогда не используется, и так далее. Кроме того, в составе PMD присутствует CPD(copy-paste-detector), который может находить дублирование кода. PMD полностью поддерживает такие языки как Java, JavaScript, XML, XSL, а CPD может работать и с более длинным списком технологий: Java, C, C + +, C #, PHP, Ruby, Fortran, JavaScript.

Стало интересно, а сколько же таких «общих недостатков» можно найти скажем в 30 приложениях Android, которые спокойно лежат себе на 4pda? 


1. Необходимо преобразовать .apk в .jar.

Для этого нужно использовать всем известную dex2jar. Использовать ее очень просто: распаковываем, да пишем примерно следующее: 

./dex2jar.sh имя_вашего_apk.apk.

После процесса преобразования в вашей папке вы найдете файл — «имя_вашего_apk_dex2jar.jar». В этом архиве уже лежат классы, необходимые для последующего анализа, но в не читабельном виде, так сказать. Для того, чтобы мы могли как следует покопаться в нормальных классах, нужно их декомпилировать. 


2. Преобразовываем код из бинарников в читаемый вид.


Из всех проверенных мной преобразователей самым удачным выглядит fernflower. Чтобы воспользоваться им, нужно выполнить команду: 

java -jar fernflower.jar имя_вашего_apk_dex2jar.jar имя_директории

Создастся папка с названием «имя_директории», в которой будет лежать файл с таким же именем, как и исходный. Но в этом случае код можно анализировать.


3. Распаковка архива.


Jar и apk файлы являются архивами, так что их можно запросто распаковать с помощью утилиты unzip.

unzip имя_файла -d имя_директории

4. Натравливаем PMD.


Для анализа исходного кода PMD использует определенный набор правил. Весь список можно глянуть вот тут: правила. Но мы будем использовать всего два набора: android и basic. Для анализа нужно прописать следующую строчку:

./run.sh pmd -d + имя_директории_с_исходниками -f text -R rulesets/java/basic.xml,rulesets/java/android.xml -version 1.7 -language java

В общем — то все, осталось лишь объединить все шаги в единый скрипт и подвести статистику.


5. Пишем простой скрипт на python. 


Сначала обговорим структуру проекта. В корне пусть будут папки: apks(куда мы положим 30 наших произвольных apk'ашек), dex2jar(где будет лежать распакованная одноименная программа), pmd(куда распакуем архив с pmd), наш сприпт. 



import commands
import os
import fnmatch
import matplotlib.pyplot as plt
import collections


abs_path_dir = os.getcwd()
read_jars_folder_name = abs_path_dir + "/apks/read_jars"
text_file_errors = open('all_errors', 'w')


def ensure_dir(f):
    d = os.path.dirname(f)
    if not os.path.exists(d):
        os.makedirs(d)


def covert_to_jar(name_apk):
    os.system(abs_path_dir + "/dex2jar/dex2jar.sh " +
              os.getcwd() + "/apks/" + name_apk)


def conver_to_read_jar(name_file, name_folder):
    full_dir_name = abs_path_dir + "/apks/" + name_file
    path = "java -jar " + abs_path_dir + "/projects/fernflower.jar " + \
        full_dir_name + " " + name_folder
    os.system(path)


def get_all_files_with_pattern(pattern, folder):
    files = []
    for f in os.listdir(folder):
        if fnmatch.fnmatch(f, pattern):
            files.append(f)
    return files


def convert_all_apk_to_jar():
    for f in get_all_files_with_pattern("*.apk", abs_path_dir + "/apks/"):
        covert_to_jar(f)


def convert_all_jar_to_read_jar():
    ensure_dir(read_jars_folder_name)
    print get_all_files_with_pattern("*.jar", abs_path_dir + "/apks/")
    for f in get_all_files_with_pattern("*.jar", abs_path_dir + "/apks/"):
        conver_to_read_jar(f, read_jars_folder_name)


def unpack_all_jars_to_projects():
    files = get_all_files_with_pattern("*.jar", read_jars_folder_name)
    for f in files:
        path = "unzip " + read_jars_folder_name + "/" + \
            f + " -d " + abs_path_dir + "/projects/" + f[:-4]
        os.system(path)


def global_convert_to_projects():
    convert_all_apk_to_jar()
    convert_all_jar_to_read_jar()
    unpack_all_jars_to_projects()
    analysis_all_projects()


def project_analysis(folder):
    path_to_pmd = abs_path_dir + "/pmd/bin/"
    command = path_to_pmd + "run.sh pmd -d " + folder + \
        " -f text -R rulesets/java/basic.xml,rulesets/java/android.xml -version 1.7 -language java"
    status, output = commands.getstatusoutput(command)
    text_file_errors.write(output)


def analysis_all_projects():
    dir_name_proj = abs_path_dir + "/projects/"
    for folder in os.listdir(dir_name_proj):
        project_analysis(dir_name_proj + folder)
    text_file_errors.close()


def get_end_words(x, i):
    answer = ""
    for i in xrange(int(i), len(x)):
        answer += x[int(i)] + " "
    return answer


def delete_small_values(d):
    b = {}
    for k, v in d.iteritems():
        if v > 2:
            b[k] = v
    return b


def get_str_dict(d):
    s = ""
    for k, v in d.iteritems():
        s += str(k) + " - " + str(v) + "\n"
    return s


def draw_graph(d):
    d = delete_small_values(d)
    d = collections.OrderedDict(sorted(d.items()))
    text_statistic = open("statistic.txt", "w")
    text_statistic.write(get_str_dict(d))
    text_statistic.close()
    plt.bar(range(len(d)), d.values(), align='center')
    plt.xticks(range(len(d)), d.keys())
    plt.show()


def get_all_errors():
    strings = open("all_errors", "r+")
    warnings = {}
    for string in strings.readlines():
        x = string.split()
        key = ""
        if len(x[1]) > 2:
            key = get_end_words(x, 1)
        else:
            key = get_end_words(x, 2)

        if key in warnings:
            warnings[key] += 1
        else:
            warnings[key] = 1
    # print warnings
    return warnings


def main():
    global_convert_to_projects()
    text_file_errors.close()
    draw_graph(get_all_errors())


if __name__ == '__main__':
    main()



После работы скрипта, мы получим как статистику в текстовом виде, так и диаграмму(диаграмма строится при помощи самого популярного средства под Python — matplotlib). 


6. Что же все — таки получилось? 


А вышло примерно следующее: 


Avoid empty synchronized blocks — 2099

Avoid instantiating Boolean objects; reference Boolean.TRUE or Boolean.FALSE or call Boolean.valueOf() instead. — 269
Avoid using a branching statement as the last in a loop. — 170
Dont create instances of already existing BigInteger and BigDecimal (ZERO, ONE, TEN) — 10
Empty initializer was found — 41
Empty static initializer was found — 41
Ensure you override both equals() and hashCode() — 149
Invoke equals() on the object you've already ensured is not null — 9
Overriding method merely calls super — 310
The null check here is misplaced; if the variable is null there will be a NullPointerException — 10
These nested if statements could be combined — 22
Unnecessary final modifier in final class — 7501
Useless parentheses. — 3582
An empty statement (semicolon) not part of a loop — 4144
Do not hard code the IP address — 101
Do not hardcode /sdcard. — 11
Do not use if statements that are always true or always false — 21
An operation on an Immutable object (String, BigDecimal or BigInteger) wont change the object itself — 4
super should be called at the end of the method — 72
super should be called at the start of the method — 86

image


Выводы можете делать сами. Мой же вывод такой:

1. PMD достоин того, чтобы использовать его повсеместно, так как таким образом можно избежать не только дублирование кода и создание неиспользуемых переменных, но и выявить более серьезные ошибки. 
2. Прогоняйте ваш код через обфускатор. Желательно всегда.

Ссылки:

PMD: pmd

dex2jar: dex2jar