Introduction

3-way-merge(henceforth 3WM) is popularly used at SCM(Source code Configuration Management) tool. But, there is no standard way of implementation. And most of them is for text(source code). So, there are already lots of study regarding Text-3WM. But, for me, it's very difficult to find study regarding Json-3WM. Someone may think Json-3WM is subset of Text-3WM. And I think it can be one of possible algorithm of Json-3WM. But, is it enough? This article is for this topic.

Assumption

Array vs. Object

At Json, Array is considered as Object. At Javascript, typeof [] gives object. For example, [1, 2] can be represented as {0:1, 1:2}. In this article, Array is handled as one of primitive value, not an object - just like JSON-Merge-Patch(RFC 7386).

Merge

Way of merging two Json object is clearly defined at JSON-Merge-Patch(RFC 7386). Therefore this article is only focusing on 3-way-mergee.

3-Way Merge: Clear

In most common cases, it looks clear. For example,

Mergable

<base>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "g"
    }
}

<our>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "g",
        "h": "i"  # <=
    }
}


<their>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "z"  # <=
    }
}

<merged>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "z",  # <=
        "h": "i"   # <=
    }
}

Conflict

<base>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "g"
    }
}

<our>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "z"  # <=
    }
}


<their>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "y"  # <=
    }
}

3-Way Merge: Not clear

This is main topic of this discussion. Consider following example

<base>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "g"
    }
}

<our>
{
    "a": "b",
    "c": {
        "d": "e",
        "f": "z"  # <=
    }
}

And

<their>
{
    "a": "b"
              # <=
}

It seems conflict. Then, which property is conflicted? /c or /c/f?

Case: /c/f (Incorrect)

In this interpretation, merged result with Accept-Our option (resolve strategy for conflicted property) should be

<merged: accept-our>
{
    "a": "b",
    "c": {
        "f": "z"  # <=
    }
}

What is issue of above? In this case, there is no difference from below version of <their> object.

<their>
{
    "a": "b",
    "c": {}  # <=
}

In case of above version, everything looks clear. Definitely, /c/f is property conflicted. Then, Is it natural that two different changes gives same merged result? I think No. Therefore this is not correct interpretation of given example.

Case: /c

In this case, interpretation of changes are

  • <their>: Deleting /c.
  • <our>: Changing /c/f and /c(sub-property of /c is changed).
    So, /c is conflicted and merged result with Accept-Our option (resolve strategy for conflicted property) should be
    <merged: accept-our>(same with <our>)
    {
      "a": "b",
      "c": {
          "d": "e",
          "f": "z"
      }
    }
    Whole sub-object /c is resolved with /c of <our> object because of Accept-Our merge strategy.

Summary

This article describes private opinion related with 3-way-merge algorithm of Json object. There is NO correct answer. This is just one of suggestion.

Software개발이란, 사람의 편의를 도와주는 도구를 만드는 것과 밀접한 관련이 있다.

그리고, 이런 류의 도구들은 생산성 향상을 가져온다.


SW 엔지니어의 경우, 특히나, 수많은 소위 'Manual'한 작업들을 SW개발을 통해서 'Automation'을 하는 과정을 중요시 한다.

왜냐하면, 단순한 작업을 최소화함으로서 더 많은 시간을 '창조적인 작업'에 사용하길 원하기 때문이다.

그러나, 국내에서는 SW개발을 '창조적인 작업'으로 생각하지 않는 곳이 많다.

(여기에 대한 논란은 접어두자. 국내에서, SI와 Game쪽을 제외하고 SW가, 수익의 중심에 있는 산업은 거의 전무하다. Portal쪽은 '업계'라고 표현하기 어렵다. 대표적인 기업 2개 정도만 존재하니...)

그러다보니, 자동화나 Tool 등을 통해 기존 업무의 생산성을 향상시키면 (예를 들어, 기존 10여명이 필요한 일을 1~2명이 할 수 있게 했다면) 당해년도, 그러니까, 생산성을 향상시킨 그 해에는 좋은 평가를 받을 수 있게 되나, 그 이후에는 나머지 사람들은 소위 '할일이 없게' 된다.

즉, 다시 무언가 성과를 낼 새로운 일을 찾지 못하면, 그 이후 부터는, '나쁜' 평가를 받게 된다.


그리고, 대부분의 경우, 새로운 일을 찾는 것은 상당히 어렵기 때문에, 여유있는 사람들은, 그때 당시 업무가 과중하게 몰리는 다른 부서 혹은 분야 쪽을 투입되는 경우가 많다. 그렇게 되면, 업무가 바뀌게 되면, 대부분의 사람들이 그렇듯이, 그 사람은 새로운 환경 그리고 업무 domain에 적응해야 하고, 초반 상당기간은 '좋은 성과'를 보여주기 힘들다.


예를 들어, 대략 정리해 보자.

- 10여명의 SW 엔지니어로 구성된 team이 있다.

- 해당 team은 반복적이고 routine한 업무를 지속적으로 수행하고 있고, 이 업무는 회사내에 필수적이며, 10여명이 꾸준히 해 나가야 하는 일이다.

- 이 업무는 Automation이나, 기타 방법으로 생산성을 향상시키기가 상당히 어렵다.


Case A: 해당 업무를 지속한다. 약간씩의 improvement는 꾸준히 진행한다.

- 기본적으로 필요한 일을 지속하고 있으므로, '좋은 실적'을 보여준다고 말할 수는 없어도 '나쁜 실적'을 보여주지도 않는다. 그냥 '평이한' 평가를 일정기간 계속해서 받을 가능성이 높다.

- 해당 일에 대한 생산성 향상이 상당히 어렵다는 것이 이미 알려진 사실이므로, 약간씩의 improvement를 통해 추가적인 실적을 보여주면서 '좋은 평가'를 노려 볼수 있다.

- 해당 team은, 이미 익숙한 사람/동료/업무 환경속에서 생활하므로 관련 스트레스도 크지 않다.

- 업무에 대한 비젼이 약하므로, 이 부분에 불만이 생길 여지가 있다.


Case B: 창의적인 생각과 노력으로 생산성 향상에 성공한 경우 (필요 인력: 10여명 -> 1~2명)

- 해당 년도에 '좋은 성과'를 받을 가능성이 높다.

- 이후, 8~9 명은 당장 인력이 부족한 팀/업무 쪽으로 재배치되고, 나머지 1~2명 역시 다른 팀으로 이동될 가능성이 높다.(1~2명이 독립적인 팀으로 유지될 수는 없다.)

- 1~2 명은 생산성 향상을 가져온 새로운 환경에서, 창조적인 생각/업무 를 할 수 있는 여력이 없이 빠듯하게 기존 업무를 계속해서 수행해 나갈 것이다.

- 재배치된 8~9명은 새로운 환경/업무에 적응하면서 초반에는 '좋지 못한' 평가를 받을 가능성이 높다.


Case B 의 경우, 해당 팀의 leader는 좋은 평가와 함께, 이후에도 더 좋은 기회를 얻게 될 가능성이 있다. 그렇지만, 팀원들은 어떤가?

B의 경우를 경험해 본 사람은, 다시 이와 비슷한 상황에서, 생산성 향상/자동화 등에 강한 거부감을 가지게 된다.

(나는 실제, 이런 상황을 경험해 보았고, 대부분의 팀원은 앞서 말한대로, 이후 생산성 향상/자동화 등에 거부감을 가지게 되었다.)


생산성을 향상시키기 위한 업무에 성공한 팀/사람 이 긍적적인 결과를 경험했을때, 이후 같은 상황에서도 이를 위해 노력할 것이다. 그런데, 과연 우리 주변의 환경은 어떤가?

내가 보기에는 그리 긍정적이지 않아 보인다...

[Makefile]

general_var:= my-value

target: dep1 dep2

    <do something> $(general_var)

위의 makefile이 단독으로 사용되는 경우는, 전혀 문제가 되지 않는다. 그렇지만, makefile이 서로 서로 include되는 등의 복잡한 system에서는

경우에 따라, 위의 makefile이 parsing된 이후, general_var 의 값이 다른 값으로 바뀌게 될 수도 있다.(include된 다른 make file에서...)

이럴 경우, 'target'의 command section이 수행될 때는 'general_var'가 이미 다른 값으로 치환된 이후이기 때문에, 기대와 다른 동작을 하게 될 수 있다.

('blog의 variable expansion at command section on GNUMake 참조)

이것을 막기 위해서는 아래와 같은 형태로 make file을 구성해야 한다. 


[Makefile]

general_var:= my-value

target: private_var := $(general_var)

target: dep1 dep2

    <do something> $(private_var)

위의 makefile은, makefile이 parsing되고 target을 만들기 위한 rule이 수행되기 전에 아래와 같은 상태가 된다.


[Makefile]

general_var:= my-value

target: private_var := my-value

target: dep1 dep2

    <do something> $(private_var)


따라서, target 의 command section이 수행되기 전에, dependency section이 수행되면서 private_var값이 my-value로 set 되고

target 의 command section은 기대했던 'my-value' 값을 가지고 수행된다.

단, 아래와 같은 syntax는 "*** commands commence before first target.  Stop." error를 발생시키니 주의해야 한다.

(즉 dependency section에서 variable assignment를 수행할 경우, command section과 함께 있으면 안된다.) 


general_var:= my-value

target: private_var := my-value

    <do something> $(private_var)




[Makefile]

$(shell echo hello) # <--- (*A)

all:

    echo all

above make file gives following results.

     

Makefile:1: *** missing separator.  Stop.

error is issued.

But, we can resolve this error by changing line (*A) into below line.

dummy := $(shell echo hello)

or

$(shell echo)


What is root cause of this?

Let's see source code of 'func_shell' function in GNUMake-v4.0.


func_shell_base(...)

{

    ...

      child_execute_job (FD_STDIN, pipedes[1], errfd, command_argv, envp); <= stdout을 pipe로 redirecdtion

    ...

          EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i)); <= pipe를 읽음

    ...

          /* The child finished normally.  Replace all newlines in its output

             with spaces, and put that in the variable output buffer.  */

          fold_newlines (buffer, &i, trim_newlines);

          o = variable_buffer_output (o, buffer, i);    <= buffer값(pipe값 -> child의 stdout값)을 variable_buffer에 저장. 

    ...

}


That is, 'shell' function stores it's result(stdout) into variable_buffer.

variable_buffer is buffer that is used to store result of variable-expandsion.

What does this mean?

See below samples.


[Makefile]

$(shell echo hello) <= (*1)


[Makefile]

temp:=hello

$(temp) <= (*2)


Above analysis means, (*1) and (*2) is same expression in gnumake's point of view.


Actually, above rules are same on all other functions in gnumake.

But, most function gives empty string - that is, it doesn't write string value to stdout.

So, it doesn't write anything into variable_buffer. That's reason why above issues are not shown at other functions.

And, in gnumake's point of view, 'function' and 'variable' is same in concept.

So, we can say that 'function' is just special variable that can get arguments - a kind of 'predefined variable'.

Therefore, we should use same concept for 'variable' and 'function' to understand gnumake.


* When 'variable expansion' at Command Section, is processed at GNUMake --------------------------------------------------------------------- In GNUMake source code, command line section of each Target seems to be expanded at 'new_job()' function. In detail, 'for-loop part' under '/* Expand the command lines and store the results in LINES. */' comment. And then, first command line is fetched at 'job_next_command()' function. Then, when 'job slot(in case of using mutlple job)' is available, this command line that expansion is done, is executed on newly created child process. new_job - expand command line variables (line-by-line) - waiting until job slot is available. - job is executed.. Note that all variables in command section, are expanded before processing first command line. Note that, variables in pre-requisites and dependencies are expanded before expanding command line section. So, following gnumake code is dangerous. out/target-file: <...> ./generate-target-file.sh $@ ./postprocess.sh $(shell readlink -e $@) [ Issue ] "$(shell ...) function" part is expanded before "./generate-target-file.sh" is executed. So, af the first build, 'out/target-file' doesn't exist, "readlink -e $@" return empty string. And this may lead to unexpected result.

--- should be modified like below out/target-file: <...> ./generate-target-file.sh $@ ./postprocess.sh $$(readlink -e $@)


예를 들어, 'gitweb', 'bugzilla' 등을 service하는 server를 configuration한다고 가정하면,

apache에서, 이것들을 한 virtual host '80 port'에 설정하고, uri 로 구분하는 방법 - (*A) - 과, 아예 virtual host를 다르게 설정하는 방법 - (*B) - 두가지가 있을 것이다.

(*A)의 경우, "<domain name>/gitweb" "<domain name>/bugzilla" 이런 식으로 나누어서 한 virtual host에서 service하면 port의 낭비도 막고, 쓸데 없이 virtual host를 하나 더 사용하는 낭비도 줄일 수 있다.

그렇지만, 경우, client의 browser의 cache 정책이나 설정등에 따라, 의도치 않은 동작들이 발생하는 경우를 많이 겪었다.

따라서, 경험적으로, 이런 경우, 불편하고, 또 약간 낭비적인 요소가 있더라도 그냥, 여러개의 virtual host로 설정하는 편이 여러가지 지뢰(?)를 피할 수 있는 가장 편한 방법이다.


특정 코드 구간이 수행되는 시간을 측정해야 할 경우가 종종 생기는데, 대부분 아래와 같은 방법을 사용한다. (ex. Java)

 

long tm = System.currentTimeMillis();
< do something >
tm = System.currentTimeMillies() - tm;
System.out.println("Time : " + tm);
그렇지만, 약간만 응용을 하면 아~주~ 조금이지만, 위의 코드를 더 간단하게 만들 수 있다.

long tm = -System.currentTimeMillis();
< do something >
tm += System.currentTimeMillis();
System.out.println("Time : " + tm);

어떤가? ^_^

<출처를 기억할 수 없는 것은 생략했습니다. 혹시 문제가 되는 부분이 있다면 알려 주시기 바랍니다.>

  • 좋은 코드는 변경하기 쉽고, 나쁜 코드는 변경하기 어렵다. 그러므로 좋은 코드는 나쁜 코드가 될 때까지 변경된다.
  • “Provide mechanism not policy” : about interface design. (From the UNIX)
  • 또 뭐가 있지??? --- 당장은 생각이 안나는 관계로...

Check 'Requesting program interpreter' in 'Program Headers' section of ELF.
Simple command : readelf -l <file>

* It is impossible to get full supporting from 'configure'

Getting runtime informations - ex bytes of 'long' type, checking whether symbol 'xxx' is in the library or not. - is impossible.
-> Some releases complain this and doesn't proceed anymore.

* Some releases still refer /lib, /usr/lib, /usr/include etc.

* Using 'make check' in naive way is impossible.

* Anything else???

+ Recent posts