[Git/Github] JGit으로 포크는 어떻게 구현해야 할라나.

모르지 나도.. 정답이 어디 있간디? 있으면 나도 보고싶다.

일단 내가 구상한 방법은 git clone이다. 포크는 풀리퀘를 구현하기 위한 발판이나 마찬가지인데 나중에 풀리퀘 구현 방법에 따라 포크 구현 방법이 달라질지도 모르겠다.

“user1/project1을 보고 있는 user2가 fork 버튼을 누르면 user1/project1과 동일한 user2/project1이 생긴다.”

대충 포크의 요구사항을 기술하면 위와 같은데, 이걸 달성하기 가장 쉬운 방법은 Clone 인것 같다.

DVCS 호스팅 서버에서 user2 디렉토리에서 git clone user1/project1을 실행해서 user1/project1을 clone 하면 다음과 같은 일이 벌어진다.

user2/project1이라는 디렉토리가 만들어지고 그 안으로 walking tree가 들어오고 그 안에 .git 디렉토리가 들어온다. 그리고 레퍼런스가 두개 생긴다. 하나는 remote/origin으로 user1/project1을 가리키는 레퍼런스고, 하나는 master 브랜치인데 이건 fork 할 프로젝트의 기본 브랜치가 뭐냐에 따라 달라질 수 있지만 보통은 master겠지.

https://www.kernel.org/pub/software/scm/git/docs/git-clone.html

이제 이걸 JGit으로 할라면 어떻게 해야되냐가 문제인데.. 스택오버를 뒤지면 잘 나온다.

cloneRepository()를 호출하면 CloneCommand 타입의 객체나 나온다. 나머진 API 문서 보면서 코딩.

이제 git clone으로 포크 기능 만들기에 필요한 기능은 확인이 됐는데… 이제부터가 시작이다.

만약에 포크하려는 이름의 프로젝트 이름이 이미 존재한다면? 프로젝트를 삭제하면 더 디렉토리도 잘 지워지나? 더 나가서는 100명이 포크따면 똑같은 워킹 디렉토리 100개 생기는건데 이건 어떻게하지? 그냥 그렇게 생기게 해야되는건가? 아니면 뭔가 중복되는 워킹 디렉토리 수를 줄일 수 있는 방법은 없을까? 등등 많지만.. 일단은 풀리퀘에 집중.

풀리퀘는 그럼 어떻게 구현할까나? 대충은 생각해봤지만 좀 더 정리해야되서 나중으로 미뤄야겠다.

[Play] 테스트 클래스 하나만 테스트하기

왜 이런걸 알아야 되는지가 문제인데… 두가지 원인이 있다.

IDE 지원이 안좋다. 테스트 실행조차 IntelliJ에서 하는게 깔끔하지 않다. 깔끔하지 않은 정도보단 안돌아. 테스트 다 깨져. 대체 IntelliJ에다 Play 세팅 깔끔하게 하는 방법은 어딨는걸까…  이클립스는 몰라. 안쓰니까. 거기선 잘 될지도 모르고.

두번째는 느려. 너무 느려..  그래서 모든 테스트 다 돌려보는건 무리고. 내가 지금 건드리것만 잘 도는지 확인하고, 전체다 돌리는건 Push 하기전에 한번 돌려보는걸로 만족하자고.

/workspace/nforge4 > play “test-only models.ProjectUserTest”

콘솔에서는 이렇게 해야되고 play 실행해서 play 콘솔 띄운다음에는 쌍따옴표는 빼도 된다.

[Git+Github] 포크 따서 작업하다가 원본이랑 싱크 맞춰서 보내기

보통 깃헙으로 포크 따서 작업하다보면 내가 포크 따온 원본의 진도가 더 나가있는 경우가 있다. 부지런한 프로젝트일 경우엔 자주 그러는데… 그럴땐 원본에 push하려는 branch의 HEAD 위에 내가 지금까지 작업한 코드를 올려서 보내는게 좋다. 그래야 내가 보낸 풀리퀘를 머지하려는 쪽에서 별다른 문제없이 머지할 수 있지.. 안그러면 지금 원본이랑 내가 보낸거랑 충돌난걸.. 받는 쪽에서 처리해야 되는데.. 그런짓은 코드 기여가 아니라 괴롭히기 수준이 되는거다.

그러나.. 그렇게 해야하는지 누가 알겠나.. 모르지.. 그러니까 코드 기여하는 방법같은 가이드를 잘 제공해서 최소한 풀리퀘 하기전에 이러 저러한걸 해달라고 문서화 해두는게 오픈 소스 프로젝트 입장에서는 최선이고, 기여하려는 사람 입장에서는 그 문서를 잘 읽고 따라주는게 서로간에 할 일인거 같다.

스프링만해도 그런 문서가 있다.

https://github.com/SpringSource/spring-framework/blob/master/CONTRIBUTING.md

스프링에 코드 기여하기 가이드는 나중에 자세히 살펴보기로하고.. 깃+깃헙이나 공부하자.

먼저 깃헙에는 nforge/hive 프로젝트가 있고 그걸 포크딴 keesun/hive 프로젝트가 있다.

내가 작업할 기능은 프로젝트 첫화면에서 프로젝트와 관련있는 히스토리, 소스 URL, 해결/미해결 이슈 개수 등을 보여주는거고, 이슈는 JIRA로 관리하고 있다.

먼저 깃헙/keesun/hive에 있는걸 로컬로 clone을 받아서 JIRA 이슈 번호로 브랜치를 딴다.

git branch HIVE-48

git checkout HIVE-48

그럼 HIVE-48이라는 브랜치가 생기고 그 브랜치로 이동해서 작업을 시작한다.

git commit -m “1”

git commit -m “2”

편의상 두번 저렇게 커밋했다고 치자. 물론 커밋 메시지는 잘 적는게 좋다. 난 지금 편의상 1번 커밋 2번 커밋으로 부를라고 일부러 저렇게 적은거다. 그런데 사실 지금은 대강 적어도 된다. 어차피 지금까지는 나만 보는거니깐… 나만 알아볼 수 있음 머라고 적던지 상관없다.

이 두개의 커밋으로 작업이 끝났다고 생각해보자. 이제 이걸 깃헙/nforge/hive의 master 브랜치로 풀리퀘를 보내고 싶으면 어찌해야 할까…

먼저 깃헙/keeun/hive로 올려야한다. 본인의 깃헙으로 먼저 올리는거다. 그런데 여기서도  깃헙/keesun/hive의 어떤 브랜치로 올리느냐가 중요한데, master로 올리지 말고 지금 쓰고 있는 브랜치를 그대로 올리는게 경험상 좋다. master 브랜치는 nforge/hive master 브랜치를 트래킹하는 브랜치로 쓰는게 어떨까싶어서인데… 뭐 맘대로. 난 HIVE-48 브랜치로 올리기로 결정했다.

git push origin HIVE-48

이렇게하면 깃헙/keeun/hive에 HIVE-48이라는 브랜치가 생기면서 내가 작업했던 두개의 커밋이 올라간다.

자 이제부터 복잡해진다.

풀리퀘를 누를라고 봤더니… 깃헙/nforge/hive에 그새 커밋이 생겼다. 내껄 보낼수가 없다. 물론 깃헙에서 풀리퀘 버튼은 눌리니까 그냥 보내도 되지만 그게 예의가 아니라고 글 처음에 적었다. 그러니까 싱크를 맞춰서 다시 보내야 하는거다. 다행히도 아직 풀리퀘를 보내기 전이다.

싱크부터 맞춰보자. 싱크를 맞추는 방법은 merge도 있고 rebase도 있지만 난 rebase가 좋다.

http://dogfeet.github.io/articles/2012/git-merge-rebase.html

왜 좋은지는 위에 글에 잘 dogfeet님께서 잘 설명해주셨으니 살펴보시길… 패스.

git fetch nforge

참.. 여기서 nforge는 내가 로컬에 등록한 값이다.

git remote add nforge 깃헙/hive/nforge.git

뭐 이런식으로 등록해둔 값이다.

암튼, 패치를 받아오면 트래킹 브랜치라는 곳에 nforge 최신 내용이 들어온다.

http://dogfeet.github.io/articles/2012/git-tracking-vs-remote-tracking.html

트래킹 패치도 dogfeet님글이 이해하기 좋다.

이제는 rebase를 한다.

git rebase nforge/master

nforge/master 위로 내 HEAD를 올리겠다는거다. 올리면서 충돌날꺼 있으면 충돌날꺼고 운좋게 충돌날꺼 없으면 아무일없이 끝날꺼다.

이제 다시 깃헙/keesun/hive로 Push를 해야 하는데 방금 작업한거 때문에 HEAD 밑에 있는 커밋 히스토리가 바껴서 push가 안될꺼다. 그럴때는 강제 push.

git push -f origin HIVE-47

이렇게 해버리면 깃헙/keesun/hive로 지금 로컬에 있느 HIVE-47 커밋 히스토리 그대로 푸쉬된다.

자 이제 어려운거 하나는 지나갔고… 문제는 커밋 두개가 하나의 작업이고 이걸 그냥 풀리퀘해도 되긴하는데… 깔끔한걸 좋아하는 분들은 커밋 여러개를 하나로 합쳐서 하나의 커밋으로 보내달라고 하길 원할꺼다. 스프링도 그렇다. 그런 작업을 squash라고 부르나 본데 어원은 모르겠고 스쿼시를 하려면 이번에도 rebase를 써야된다.

가장 최신 커밋 두개를 하나로 합치면 되니까..

git rebase -i HEAD~2

이렇게 하면 vim 편집창 같은게 뜨면서 위에 커밋 로그 두개가 보인다. 그중에서 한쪽으로 합쳐버리고 싶은걸 pick 대신 s나 squash로 바꿔주면 된다. 사실 그 화면에서 특정 커밋을 빼버릴 수도 있고 커밋 순서를 바꿀 수도 있고.. 여러가지 작업을 할 수 있다.

http://ko.gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html

단순히 합치는거라면 이글이 도움이 되고…다른 여러 기능에 대해서는

http://git-scm.com/book/en/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages

이 글이 좋다.

커밋 하나를 s로 바꾸고 wq! 하면 또 다시 편집창이 뜨는데 이번에는 커밋 메시지를 고치는 과정이다 잘 바꾸고.. wq! 하면 rebase 끝.

이제 다시 강제 push를 해야하니까..

git push -f origin HIVE-48

오.. 이제야 풀리퀘 준비가 끝났다. 싱크도 맞췄고 커밋도 스쿼시 했으니… 이제 깃헙으로 풀리퀘 보내면 된다.

ps: 단순히 어떤 툴에 포크/풀리퀘 기능이 있다고 협업이 쉽게 될꺼라 생각하는건 큰 착각이었다.

[스프링 MVC 3.2] Async Servlet

작년 스프링 원 발표 동영상이다. 여기서 설명하는 Async Servlet 관련 내용만 정리한다. 모르지 보다가 내키면 다른것도 하고.. 아님 안하고 내맘.

우선 이녀석이 등장하게 된 배경부터 설명한다. (나말고 발표자가. 아마도 로센?)

Request-Response 모델은 누구나 다 알고 지금도 많이 사용하듯이 웹브라우저에서 사용자가 뭔가 요청을 보내면 서버가 응답해주는 방식이다.

그러다 Ajax가 등장한다. 전체 페이지를 로딩해오는게 아니라 페이지 일부만 변경하는 방법이다. 기술적으로는 자바스크립트로 Request-Response를 처리하는 거고, 자바스크립트로 setTimeout인가 뭔가를 사용해서 주기적으로 요청을 보내서 응답을 받은걸로 화면 일부를 갱신하는 기술이 가능해졌었다.

그런게 바로 ‘live’-ness 인데, 굳이 옮기자면 “생동감”이라고나 할까. 애플리케이션마다 각기 다른 수준의 생동감이 필요하다.

트위터나 페북에 올라오는 스트림을 주기적으로 가져오거나, 최신 뉴스를 주기적으로 가져오는 것 정도야 괜찮겠지만, 채팅이나 온라인 협업 같은 경우라면 10초, 5초 주기로 갱신해가며 대화하기는 불편할꺼다. 거의 실시간적인 생동감이 필요하다.

그래서 등장한게 Long Polling. 이것도 사실 Request-Response 모델이다. 단 하나 다른게 있다면 Response를 바로 하는게 아니라 클라이언트로 줄 만한 데이터가 있을때까지 서버가 요청을 붙잡고 안놔준다는거… 그러다 필요한 데이터가 오면 그때 클라이언트로 응답을 보내고, 응답을 받은 클라이언트는 곧바로 다음번 요청을 보내서 또 서버한테 데이터 있을 때까지 가지고 있다가 응답 달라고 하는거다. 이걸 이제 얼핏 비껴보면 서버가 클라이언트로 Push하는거처럼 보이기도 하는거지.

그런데 이게 문제가 뭐냐면, 데이터가 바로바로 올라오면 상관없는데, 클라이언트한테 줄 데이터가 안오면 서버 리소스가 계속해서 붙잡힌 상태로 대기 중이기 때문에 서버쪽에 부하가 상당해진다.

일반적으로 서블릿 컨테이너는 “Thread-Per-Request” 모델을 사용하는데, 롱폴링이 많아지면 요청을 잡고있는 Thread가 많아져서 새로운 요청을 받아줄 쓰레드가 금방 모자르게 되는거지.. 두둥. 그래서!! 서블릿 3 어싱크 기능이 등장!

어떻게 할려는거냐면… 단순하게 말해서 쓰레드를 바꿔치기 하려는거지..

대기해야 하는 작업을 Request 처리하는 쓰레드 말고 다른 쓰레드(흔히 워커쓰레드라고 부름)한테 위임시키고 Request 쓰레드는 빨리 반납해서 그 자원으로 새로운 Request를 처리하려는거지.

그래야 똑같은 방식(롱폴링)으로 동작하면서도 더 많은 요청을 처리할 수 있을테니까.

서블릿 쓰레드 모델을 표현하면 다음과 같다.

[Servlet Thread]

– Start processing

– Do some work

– Send response

완전 단순하지.. 근데 이걸 이제 중간에 끊어서 다른 놈한테 주려는거야… 요렇게.

[Servlet Thread]

– Start processing

– Switch to async mode

– Exit thread leaving response open

[Application Thread]

– Produce or receive result

– Dispatch to container to resume processing

[Servlet Thread]

– Start processing

– Process result from application thread

– Send response

복잡해보이지만, 사실 단순하지. 서블릿 쓰레드가 요청 처리 시작하면 어싱크 모드로 바꾸고 쓰레드는 응답하지 않고 끝내버리지, 그럼 서블릿 쓰레드는 반나되고 다른 요청 처리하겠지뭐. 그런 어싱크 처리는 애플리케이션 쓰레드가 처리하고 그 결과를 다시 컨테이너한테 주는거야. 그럼 컨테이너가 다시 서블릿 쓰레드 띄워서 애플리케이션 쓰레드가 보내준 결과 받아서 응답 만들어서 보내는거지.

여기서 질문… 왜 애플리케이션 쓰레드가 바로 응답을 보내지 않고 다시 서블릿 쓰레드를 거쳐서 보내느냐…

이건 마일스톤1에서 시도해봤었는데 안돼. HttpServeltRequest를 컨테이너 쓰레드가 아닌곳에서는 사용할 수가 없어서 포워딩(JSP 렌더링할때 하는 포워딩 알지?)을 할 수가 없어. 암튼 그래서안되고. 스프링 3.2 어싱크나 보자고.

@RequestMapping 메서드의 리턴값으로 Callable랑 DefferedResult, AyncTask를 던질 수 있게됐어.

이 중에서 Callable부터 볼까나

java.util.concurrent.Callable

이 객체 담긴 작업은 스프링 MVC가 관리하는 쓰레드에서 돌게 돼. 주로 오래 걸리는 DB 작업, 써드파티 REST API 콜 등에 적당하다네..

DefferedResult

이 객체 담긴 작업은 스프링 MVC 밖에 있는 쓰레드에서 돌게 돼지. 주로 JSM, AMQP 리스너, 또 다른 HTTP 요청을 보내거나. 등등

AsyncTask

Callable하고 같은데 비동기 처리에 타임아웃 값을 추가할 수 있다는군. AsuncTaskExecutor를 선택할 수도 있고. 용도에 따라 여러 쓰레드풀을 두고 선택해서 사용할 수 있다네.. 페이스북용 쓰레드풀, 트위터용 쓰레드풀.. 하는식으로.

질문: 응답을 찔끔 찔끔 보내고 싶은데.. 그때는 어떻게 해?

답: OutputStream에 접근할 수 있으니까 그거 가지고 찔끔 찔끔 보내면 되긴 하는데 JSON은 어쩌고 저쩌고.. HTTP 스트리밍을 하려면 그건 좀 다른건데.. 어쩌구 저쩌구.. 나중에 볼께.

DEMO는 스프링 MVC Showcase에 있음

async  패키지에 CallableController를 보면 됨.

https://github.com/SpringSource/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/CallableController.java

@RequestMapping(“/response-body”)
public @ResponseBody Callable<String> callable() {
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return “Callable result”;
}
};
}
@RequestMapping(“/view”)
public Callable<String> callableWithView(final Model model) {
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
model.addAttribute(“foo”, “bar”);
model.addAttribute(“fruit”, “apple”);
return “views/html”;
}
};
}
이런 메서드인데 저렇게 Callable 객체로 만든 부분이 서블릿 쓰레드가 아니라 스프링 MVC에서 관리하는 쓰레드에서 실행되는거지. 그리고 리턴되면 다시 서블릿 쓰레드가 받아서 처리하는거고.. 그런 일련의 과정은 로그를 보면 알 수 있고.

자.. 이게 문제의 화면인데. 사부님이 여기서 아주 심각한 문제를 발견하셨지!!! 캬캬캬. 역시 내가 아는 가장 성실하고 인간적이면서 실력까지 최고인 엔지니어야… 왜 단점이 없을까.. 뚱뚱한거?… 암튼, 이 부분에 대해서는 다음주 세미나 때 설명하실테니까 나는 패스.

질문: 쓰레드 스위칭이 일어나는데 성능에 좀 영향이 없냐..

답: 쓰레드 풀에 다양한 옵션이 있으니까 풀 옵션으로 조정하고, 약간의 burden이 있긴 하지만 서블릿 쓰레드를 쉬게하는게 중요하다.

질문: 브라우저는 그거다 끝날때까지 계속 대기중이지?

답: 그렇다.

이 시점에서 질문이 쏟아진다… 다는 못알아 듣겠다..

Callable에서 예외를 던지면 어떻게 되냐..

@RequestMapping(“/exception”)
public @ResponseBody Callable<String> callableWithException(
final @RequestParam(required=false, defaultValue=”true”) boolean handled) {
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
if (handled) {
// see handleException method further below
throw new IllegalStateException(“Callable error”);
}
else {
throw new IllegalArgumentException(“Callable error”);
}
}
};
}

HandlerExceptionResolver로 해결하지요.. 다른 MVC에서 발생하는 예외랑 마찬가지로.

그럼 스프링 MVC 밖에서 동작하는 DefferedResult에서 발생하는 예외는?

https://github.com/SpringSource/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/DeferredResultController.java

여기 코드 잘 보면 나오는데, setErrorResult(object)로 예외를 세팅하게 되있고, 그걸 나중에 dispatch 하고나서 역시 HandleExceptionResolver로 해결한다네..

이런거 쓰려면 web.xml에 async-supported 플래그 설정해줘야돼.

OncePerRequestFilter 등 모든 스프링 MVC 필터들이 업데이트 됐다는군. 이건 어싱크 서블릿 스팩이랑 관련있는 내용이로군.

AsyncHandlerInterceptor라는게 추가됐구나;; 자세한건 나중에 자바독을 보기로하고 패스

Async Support 설정하려면 자바 설정이랑 MVC 네임스페이스가 있으니까 알아서..

뭘 설정할 수 있냐면.

– Async timeout value (밀리세컨드)

– Callable 처리할 AysncTaskExecutor

모든 필터에 async-supported 플래그가 있으니까 설정하고 dispatcher-type도.

WebApplicationInitializer로 설정하는 방법 소개하는데..  난 자바 설정을 잘 안써서… 흠;; 패스

이 뒤부터는 REST 관련 내용이로군… REST 할때의 예외처리에 관한 내용인데 이것도 관심이 가네!! 오.. 호… 그래 REST API는 예외처리가 참 난감했는데 어찌한담… 이건 설렁설렁 봐야지.