Git 사용 방법 요약

|

다양한 형상 관리 툴이 나왔지만, 요즘은 Git을 많이 사용하고 있는 것 같습니다.

Git은 크게 master 저장소를 가지는 서버와 master 저장소의 완벽한 사본을 가지는 클라이언트 저장소로 구성되어 있습니다.

따라서 로컬 저장소 사용 방법과 원격 저장소 사용 방법을 나누어서 설명하도록 하겠습니다.


로컬 저장소 사용을 위한 명령어

로컬 저장소에서는 다음과 같은 명령어를 사용할 수 있습니다.

명령어 설명
git init 현재 위치에 git 저장소를 생성
git add 파일이름 git에 파일을 등록
git commit git 저장소에 변경 이력 제출
git status git 저장소의 상태 확인

만약 큰 수정을 위해 별도로 브랜치(branch)를 생성해서 작업을해야 한다면 다음 명령어를 이용할 수 있습니다.

명령어 설명
git branch 이름 새로운 브랜치를 생성
git checkout 이름 해당 브랜치로 변경
git merge 이름 현재 브랜치에 ‘이름’의 브랜치 내용을 정합

브랜치를 활용한 작업 흐름

image


원격 저장소 사용을 위한 명령어

명령어 설명
git clone url 원격 저장소(URL)의 내용을 로컬 저장소에 복사
git remote 원격 저장소와 로컬 저장소를 연결
git push 로컬 저장소의 내용을 원격 저장소에 업로드
git fetch 로컬 저장소와 원격 저장소의 변경 내용이 다를 때 이를 비교/대조. git merge 명령어와 함께 최신 데이터를 반영하거나 충돌 문제를 해결
git pull 로컬 저장소의 내용을 원격 저장소의 최신 내용으로 업데이트

예를 들면, git clone은 다음과 같은 형태로 사용 가능합니다.

git clone https://github.com/snowdeer/bitcoin

git push의 경우는 다음과 같이 사용할 수 있습니다.

git push [원격 저장소 별칭] [로컬 브랜치 이름]

원격 저장소에 로컬 저장소의 모든 브랜치를 업로드할 때는 아래와 같이 사용할 수 있습니다.

git push origin --all


fetch와 pull

원격 저장소의 내용을 로컬 저장소로 가져와서 정합하는 방법은 크게 git fetchgit pull 두 가지가 있습니다.

pull의 경우는 원격 저장소의 내용을 가져오면서 로컬 저장소에 자동으로 정합을 해줍니다. 다만, 이 경우는 어떤 내용이 변경되었고 정합되었는지 확인하기가 어렵습니다. 따라서 원격 저장소에서 무조건 pull로 데이터를 가져오는 것은 추천하지 않는 방법입니다.

fetch의 경우는 원격 저장소의 commit을 가져와서 로컬 저장소에서 이를 확인한다음 수동으로 정합할 수 있습니다.

경우에 따라 fetchpull을 적당히 혼용해서 사용하면 될 것 같습니다.

ini 파일 읽어들이기

|

ini 파일은 각종 설정값 등을 정의한 설정 파일로, de facto 표준입니다. 단순 텍스트 파일로 되어 있어서 수정이 편리합니다.


ini 파일 예제

ini 파일은 다음과 같은 형태로 되어 있습니다.

; Test ini file

[version]                   ; INI file version
version = 1.02
version_name = Hello, snowdeer

[device_config]             ; Device configuration
mic_enabled = false
speaker_enabled = false
camera_enabled = true
motion_enabled = true


다음 헤더 파일만 프로젝트에 첨부하면 됩니다.

INIReader.h


#ifndef __INI_H__
#define __INI_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>

typedef int (*ini_handler)(void *user, const char *section,
                           const char *name, const char *value);

typedef char *(*ini_reader)(char *str, int num, void *stream);

int ini_parse(const char *filename, ini_handler handler, void *user);

int ini_parse_file(FILE *file, ini_handler handler, void *user);

int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler,
                     void *user);

#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif

/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
   the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif

/* Nonzero to allow inline comments (with valid inline comment characters
   specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
   Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif

/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif

/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif

/* Maximum line length for any line in INI file. */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif

#ifdef __cplusplus
}
#endif

/* inih -- simple .INI file parser

inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:

https://github.com/benhoyt/inih

*/

#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#if !INI_USE_STACK
#include <stdlib.h>
#endif

#define MAX_SECTION 50
#define MAX_NAME 50

/* Strip whitespace chars off end of given string, in place. Return s. */
inline static char *rstrip(char *s) {
  char *p = s + strlen(s);
  while (p > s && isspace((unsigned char) (*--p)))
    *p = '\0';
  return s;
}

/* Return pointer to first non-whitespace char in given string. */
inline static char *lskip(const char *s) {
  while (*s && isspace((unsigned char) (*s)))
    s++;
  return (char *) s;
}

/* Return pointer to first char (of chars) or inline comment in given string,
   or pointer to null at end of string if neither found. Inline comment must
   be prefixed by a whitespace character to register as a comment. */
inline static char *find_chars_or_comment(const char *s, const char *chars) {
#if INI_ALLOW_INLINE_COMMENTS
  int was_space = 0;
  while (*s && (!chars || !strchr(chars, *s)) &&
      !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
    was_space = isspace((unsigned char) (*s));
    s++;
  }
#else
  while (*s && (!chars || !strchr(chars, *s))) {
        s++;
    }
#endif
  return (char *) s;
}

/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
inline static char *strncpy0(char *dest, const char *src, size_t size) {
  strncpy(dest, src, size);
  dest[size - 1] = '\0';
  return dest;
}

/* See documentation in header file. */
inline int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler,
                            void *user) {
  /* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
  char line[INI_MAX_LINE];
#else
  char* line;
#endif
  char section[MAX_SECTION] = "";
  char prev_name[MAX_NAME] = "";

  char *start;
  char *end;
  char *name;
  char *value;
  int lineno = 0;
  int error = 0;

#if !INI_USE_STACK
  line = (char*)malloc(INI_MAX_LINE);
    if (!line) {
        return -2;
    }
#endif

  /* Scan through stream line by line */
  while (reader(line, INI_MAX_LINE, stream) != NULL) {
    lineno++;

    start = line;
#if INI_ALLOW_BOM
    if (lineno == 1 && (unsigned char) start[0] == 0xEF &&
        (unsigned char) start[1] == 0xBB &&
        (unsigned char) start[2] == 0xBF) {
      start += 3;
    }
#endif
    start = lskip(rstrip(start));

    if (*start == ';' || *start == '#') {
      /* Per Python configparser, allow both ; and # comments at the
         start of a line */
    }
#if INI_ALLOW_MULTILINE
    else if (*prev_name && *start && start > line) {

#if INI_ALLOW_INLINE_COMMENTS
      end = find_chars_or_comment(start, NULL);
      if (*end)
        *end = '\0';
      rstrip(start);
#endif

      /* Non-blank line with leading whitespace, treat as continuation
         of previous name's value (as per Python configparser). */
      if (!handler(user, section, prev_name, start) && !error)
        error = lineno;
    }
#endif
    else if (*start == '[') {
      /* A "[section]" line */
      end = find_chars_or_comment(start + 1, "]");
      if (*end == ']') {
        *end = '\0';
        strncpy0(section, start + 1, sizeof(section));
        *prev_name = '\0';
      } else if (!error) {
        /* No ']' found on section line */
        error = lineno;
      }
    } else if (*start) {
      /* Not a comment, must be a name[=:]value pair */
      end = find_chars_or_comment(start, "=:");
      if (*end == '=' || *end == ':') {
        *end = '\0';
        name = rstrip(start);
        value = lskip(end + 1);
#if INI_ALLOW_INLINE_COMMENTS
        end = find_chars_or_comment(value, NULL);
        if (*end)
          *end = '\0';
#endif
        rstrip(value);

        /* Valid name[=:]value pair found, call handler */
        strncpy0(prev_name, name, sizeof(prev_name));
        if (!handler(user, section, name, value) && !error)
          error = lineno;
      } else if (!error) {
        /* No '=' or ':' found on name[=:]value line */
        error = lineno;
      }
    }

#if INI_STOP_ON_FIRST_ERROR
    if (error)
            break;
#endif
  }

#if !INI_USE_STACK
  free(line);
#endif

  return error;
}

/* See documentation in header file. */
inline int ini_parse_file(FILE *file, ini_handler handler, void *user) {
  return ini_parse_stream((ini_reader) fgets, file, handler, user);
}

/* See documentation in header file. */
inline int ini_parse(const char *filename, ini_handler handler, void *user) {
  FILE *file;
  int error;

  file = fopen(filename, "r");
  if (!file)
    return -1;
  error = ini_parse_file(file, handler, user);
  fclose(file);
  return error;
}

#endif /* __INI_H__ */


#ifndef __INIREADER_H__
#define __INIREADER_H__

#include <map>
#include <set>
#include <string>

// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
// for simplicity here rather than speed, but it should be pretty decent.)
class IniReader {
 public:
  // Construct IniReader and parse given filename. See ini.h for more info
  // about the parsing.
  IniReader(std::string filepath);

  // Return the result of ini_parse(), i.e., 0 on success, line number of
  // first error on parse error, or -1 on file open error.
  int parseError();

  // Return the list of sections found in ini file
  std::set<std::string> sections();

  // getString a string value from INI file, returning default_value if not found.
  std::string getString(std::string section, std::string name,
                        std::string default_value);

  // getString an integer (long) value from INI file, returning default_value if
  // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
  long getLong(std::string section, std::string name, long default_value);

  // getString a real (floating point double) value from INI file, returning
  // default_value if not found or not a valid floating point value
  // according to strtod().
  double getDouble(std::string section, std::string name, double default_value);

  // getString a boolean value from INI file, returning default_value if not found or if
  // not a valid true/false value. Valid true values are "true", "yes", "on", "1",
  // and valid false values are "false", "no", "off", "0" (not case sensitive).
  bool getBool(std::string section, std::string name, bool default_value);

 private:
  int _error;
  std::map<std::string, std::string> _values;
  std::set<std::string> _sections;
  static std::string makeKey(std::string section, std::string name);
  static int valueHandler(void *user, const char *section, const char *name,
                          const char *value);
};

#endif  // __INIREADER_H__


#ifndef __INIREADER__
#define __INIREADER__

#include <algorithm>
#include <cctype>
#include <cstdlib>

using std::string;

inline IniReader::IniReader(string filepath) {
  _error = ini_parse(filepath.c_str(), valueHandler, this);
}

inline int IniReader::parseError() {
  return _error;
}

inline std::set<string> IniReader::sections() {
  return _sections;
}

inline string IniReader::getString(string section, string name, string default_value) {
  string key = makeKey(section, name);
  return _values.count(key) ? _values[key] : default_value;
}

inline long IniReader::getLong(string section, string name, long default_value) {
  string valstr = getString(section, name, "");
  const char *value = valstr.c_str();
  char *end;
  // This parses "1234" (decimal) and also "0x4D2" (hex)
  long n = strtol(value, &end, 0);
  return end > value ? n : default_value;
}

inline double IniReader::getDouble(string section, string name, double default_value) {
  string valstr = getString(section, name, "");
  const char *value = valstr.c_str();
  char *end;
  double n = strtod(value, &end);
  return end > value ? n : default_value;
}

inline bool IniReader::getBool(string section, string name, bool default_value) {
  string valstr = getString(section, name, "");
  // Convert to lower case to make string comparisons case-insensitive
  std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
  if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
    return true;
  else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
    return false;
  else
    return default_value;
}

inline string IniReader::makeKey(string section, string name) {
  string key = section + "=" + name;
  // Convert to lower case to make section/name lookups case-insensitive
  std::transform(key.begin(), key.end(), key.begin(), ::tolower);
  return key;
}

inline int IniReader::valueHandler(void *user, const char *section, const char *name,
                                   const char *value) {
  IniReader *reader = (IniReader *) user;
  string key = makeKey(section, name);
  if (reader->_values[key].size() > 0)
    reader->_values[key] += "\n";
  reader->_values[key] += value;
  reader->_sections.insert(section);
  return 1;
}

#endif  // __INIREADER__


사용법

#include "IniReader.h"

#include <iostream>

int main(void) {

  IniReader reader("test.ini");

  if (reader.parseError() < 0) {
    std::cout << "Can't load 'test.ini'\n";
    return 1;
  }

  double version = reader.getDouble("version", "version", -1);
  string version_name = reader.getString("version", "version_name", "");

  printf("Version:%.2f, (%s)\n", version, version_name.c_str());

  bool mic_enabled = reader.getBool("device", "mic_enabled", false);
  bool speaker_enabled = reader.getBool("device", "speaker_enabled", false);
  bool camera_enabled = reader.getBool("device", "camera_enabled", false);
  bool motion_enabled = reader.getBool("device", "motion_enabled", false);

  printf("mic_enabled : %d, speaker_enabled: %d, camera_enabled:%d, motion_enabled:%d\n",
         mic_enabled, speaker_enabled, camera_enabled, motion_enabled);

  return 0;
}

Linux에서 외부 명령어 실행하는 동안 대기하기

|

외부 명령어 실행하는 동안 대기하기

리눅스에서 외부 명령어를 실행하는 함수는 execlp 입니다. 외부 명령어를 호출하기 때문에 해당 명령어가 어떻게 실행되고 있는지, 종료가 되었는지 알기가 힘든데 fork()wait()를 이용하면 외부 명령어의 종료 시점을 알 수 있습니다.

#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
  int pid;

  pid = fork();

  if (pid < 0) {
    printf("A fork error has occurred.\n");
    exit(-1);
  }

  if (pid == 0) {
    printf("Forking is successful.\n");
    execlp("/bin/ls", "ls", nullptr);
    exit(127);
  } else {
    printf("This is the parent.\n");    
    int status;
    waitpid(pid, &status, 0);
    printf("The child just ended\n");
  }

  return 0;
}


system() 함수

system() 함수는 내부적으로 fork(), exec(), waitpid()으로 이루어져 있기 때문에 보다 쉽게 사용할 수 있습니다.

#include <stdlib.h>

int system(const char *cmd);

명령이 정상적으로 수행될 경우 127 값을 리턴하며, 오류가 난 경우 -1을 리턴합니다. system() 함수의 내부는 다음 코드와 비슷합니다.

#include <stdio>
#include <errno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int system(const char *cmd) {
  pid_t pid;
  int status;

  if ((pid = fork()) < 0) {
    status = -1;
  }
  else if (pid == 0) {
    execl("/bin/sh", "sh", "-c", cmd, (char *) 0);
    _exit(127);
  } else {
    while(waitpid(pid, &status, 0) < 0) {
      if(errno != EINTR) {
        status = -1;
        break;
      }
    }
  }

  return status;
}

시스템콜(System call)과 작업 영역

|

대부분의 시스템은 작업 범위와 안정성을 위해 크게 두 가지의 작업 영역으로 나눕니다.

  • 커널 영역 (Kernel Space)
  • 사용자 영역 (User Space)


커널 영역 (Kernel Space)

커널 영역은 커널이 작업하는 영역으로 CPU 작업 스케줄이나 하드웨어를 제어하는 디바이스 드라이버와 메모리를 관리하는 기능 등을 수행합니다.

이러한 작업을 위해 CPU에서 제공하는 명령얻이 존재합니다. 그 중에는 `특수 명령어(Privileged Instruction)’도 존재합니다. 특수 명령어의 경우 오로지 커널에서만 사용 가능하며 일반 라이브러리에서는 호출할 수 없습니다.

커널은 가장 신뢰할 수 있는 프로그램들로만 구성되어져야 하며, 만약 커널에서 오류가 발생하면 그대로 시스템 종료로 이어집니다.


사용자 영역 (User Space)

사용자 영역은 일반 프로그램이 실행되는 영역으로 해당 프로그램만의 공간입니다. ‘샌드박스(Sand Box)’라고도 부릅니다. 사용자 영역에서는 직접 하드웨어 디바이스 또는 메모리를 제어할 수는 없습니다. 작업을 위해서는 ‘시스템콜(System call)’을 이용해 커널에 작업을 요청하고 커널의 도움을 받아 작업을 수행할 수 있습니다.


예제

예를 들어 ‘a = b + c’라는 문장을 수행하는 작업은 사용자 영역에서 수행되는 작업이지만, ‘char* buffer = (char*)malloc(100)’과 같은 메모리를 할당하거나 파일을 건드리는 작업 등은 커널의 도움을 받아야만 가능한 작업들입니다.


Operating System Trap

작업 영역의 변환은 일종의 소프트웨어 인터럽트라고 불리우는 Operation System Trap을 통해 이루어집니다. 어플리케이션의 요청과 작업에 필요한 데이터는 커널에 작업을 요청할 때, 사용자 영역에서 커널 영역으로 복사하여 전달합니다. (물론 반대의 경우도 마찬가지입니다.)

시스템 트랩이 발생할 경우, 어플리케이션에서 수행하던 모든 작업들이 중단되고 작업 권한이 커널로 넘어갑니다.

즉, 빈번한 시스템콜 호출은 전반적인 성능 하락을 가져옵니다.

콘솔창에 Colored Text 출력하기

|

Putty와 같은 터미널에서는 색상이 입혀진 텍스트를 화면에 출력할 수 있습니다. ANSI Color Code라고 하며, ANSI Escape Code의 기능 중 하나입니다. ANSI Escape Codesms 터미널에서 텍스트 포맷을 제어하기 위해 만든 코드이며, 현재 ISO/IEC-6429 표준으로 제정되어 있습니다.

대부분의 터미널에서는 이 기능을 지원하는데, Windows의 커맨드 창(Command Shell)에서는 이 기능을 지원하지 않는 것 같습니다.

ANSI Escape Code

Code Effect Note
0 Reset / Normal all attributes off
1 Intensity: Bold  
2 Intensity: Faint not widely supported
3 Italic: on not widely supported. Sometimes treated as inverse.
4 Underline: Single  
5 Blink: Slow less than 150 per minute
6 Blink: Rapid MS-DOS ANSI.SYS; 150 per minute or more
7 Image: Negative inverse or reverse; swap foreground and background
8 Conceal not widely supported
21 Underline: Double not widely supported
22 Intensity: Normal not bold and not faint
24 Underline: None  
25 Blink: off  
27 Image: Positive  
28 Reveal conceal off  

이 외에도 더 많은 ANSI Escape Code 들이 있지만, 이 중에서 텍스트의 색상을 결정하는 코드들은 30부터 39까지, 텍스트의 배경색은 40부터 49까지 존재합니다.


ANSI Color

Code Effect
30 set foreground color to black
31 set foreground color to red
32 set foreground color to green
33 set foreground color to yellow
34 set foreground color to blue
35 set foreground color to magenta (purple)
36 set foreground color to cyan
37 set foreground color to white
39 set foreground color to default (white)
40 set background color to black
41 set background color to red
42 set background color to green
43 set background color to yellow
44 set background color to blue
45 set background color to magenta (purple)
46 set background color to cyan
47 set background color to white
49 set background color to default (black)


소스 코드

위와 같은 ANSI Color 색상을 printf 함수나 cout 함수 등을 통해 쉽게 출력할 수 있도록 예제 코드는 다음과 같습니다.

Log.h

#ifndef LITOSERVICE_UTILS_LOG_H_
#define LITOSERVICE_UTILS_LOG_H_

class Log {
 public:
  static void v(const char* format, ...);
  static void d(const char* format, ...);
  static void i(const char* format, ...);
  static void w(const char* format, ...);
  static void e(const char* format, ...);

 private:
  Log() {}
  virtual ~Log() {}
};

#endif /* LITOSERVICE_UTILS_LOG_H_ */


Log.cc

#include <Log.h>
#include <cstdio>
#include <stdarg.h>

#ifdef __ANDROID__
#include <android/log.h>
#else
#include <stdarg.h>
#endif

#ifdef __ANDROID__
#define PLOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, gProseLogTag, __VA_ARGS__)
#define PLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, gProseLogTag, __VA_ARGS__)
#define PLOGI(...) __android_log_print(ANDROID_LOG_INFO, gProseLogTag,__VA_ARGS__)
#define PLOGW(...) __android_log_print(ANDROID_LOG_WARN, gProseLogTag,__VA_ARGS__)
#define PLOGE(...) __android_log_print(ANDROID_LOG_ERROR, gProseLogTag, __VA_ARGS__)
#define PLOGF(...) __android_log_print(ANDROID_FATAL_ERROR, gProseLogTag,__VA_ARGS__)
#define PLOGS(...) __android_log_print(ANDROID_SILENT_ERROR, gProseLogTag,__VA_ARGS__)
#else
#define LOGV(...)
#define LOGD(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#define LOGF(...)
#define LOGS(...)
#endif

void Log::v(const char* format, ...) {
  char message[1024] = { 0, };

  va_list lpStart;
  va_start(lpStart, format);
  vsprintf(message, format, lpStart);
  va_end(lpStart);

  printf(FCYN("[%s] %s\n"), gLitoServiceTag, message);
  LOGV("%s", message);
}

void Log::d(const char* format, ...) {
  char message[1024] = { 0, };

  va_list lpStart;
  va_start(lpStart, format);
  vsprintf(message, format, lpStart);
  va_end(lpStart);

  printf(FGRN("[%s] %s\n"), gLitoServiceTag, message);
  LOGD("%s", message);
}

void Log::i(const char* format, ...) {
  char message[1024] = { 0, };

  va_list lpStart;
  va_start(lpStart, format);
  vsprintf(message, format, lpStart);
  va_end(lpStart);

  printf(FYEL("[%s] %s\n"), gLitoServiceTag, message);
  LOGI("%s", message);
}

void Log::w(const char* format, ...) {
  char message[1024] = { 0, };

  va_list lpStart;
  va_start(lpStart, format);
  vsprintf(message, format, lpStart);
  va_end(lpStart);

  printf(FMAG("[%s] %s\n"), gLitoServiceTag, message);
  LOGW("%s", message);
}

void Log::e(const char* format, ...) {
  char message[1024] = { 0, };

  va_list lpStart;
  va_start(lpStart, format);
  vsprintf(message, format, lpStart);
  va_end(lpStart);

  printf(BOLD(FRED("[%s] %s\n")), gLitoServiceTag, message);
  LOGE("%s", message);
}