강제 푸시로 덮어쓴 커밋 되돌리기
푸시가 브랜치를 덮어쓰거나 누가 브랜치를 지우면 OwnGit은 이전 커밋을 보관된 기록에 남깁니다. 브라우저에서 저장소를 열고, 되돌리면 바뀔 내용을 모두 확인한 다음 트리 전체나 고른 파일만 되돌리면 됩니다. 코딩 에이전트가 한 푸시도 같은 방법으로 되돌립니다.
보관된 기록은 백업이 아닙니다. 실수로 푸시한 비밀값도 함께 남으므로, 유출된 비밀값은 강제 푸시로 지우려 하지 말고 새로 바꾸세요.
OwnGit은 내 컴퓨터나 NAS, 홈 서버에서 돌리는 Git 서버입니다. 강제 푸시나 삭제로 브랜치가 바뀌어도 이전 커밋을 보관하므로, 내가 한 실수든 코딩 에이전트가 한 실수든 브라우저에서 되돌릴 수 있습니다. SQLite 파일을 쓰는 프로그램 하나로 돌아가고 메모리는 45~70 MB 정도 씁니다. 가입해야 하는 계정도 없습니다.
brew install juliankang4/tap/owngit
어떤 방법이든 호스트에 git-http-backend가 포함된 Git이 있어야 합니다. Homebrew와 Arch 패키지는 Git을 함께 설치합니다.
npm install -g owngit
어떤 방법이든 호스트에 git-http-backend가 포함된 Git이 있어야 합니다. Homebrew와 Arch 패키지는 Git을 함께 설치합니다.
curl -fLO https://github.com/juliankang4/owngit/releases/latest/download/PKGBUILD && makepkg -si
어떤 방법이든 호스트에 git-http-backend가 포함된 Git이 있어야 합니다. Homebrew와 Arch 패키지는 Git을 함께 설치합니다.
GitHub Releases에서 플랫폼에 맞는 압축 파일을 내려받아 SHA256SUMS로 확인하세요. 실행 파일에는 서명이 없습니다.
$ git push --force origin main Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Writing objects: 100% (3/3), 274 bytes | 274.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) To http://127.0.0.1:18432/git/website.git + f744962...dfdad84 main -> main (forced update) $
Git 서버를 직접 운영하는 사람들이 자주 꺼내는 문제와 OwnGit이 각각을 다루는 방법입니다.
비공개 코드를 남의 클라우드에 두고 싶지 않습니다.
코드는 내 컴퓨터에 있습니다. 저장소는 내 컴퓨터나 NAS, 홈 서버에 평범한 bare Git 저장소로 저장됩니다. 클라우드 계정이나 구독은 필요 없고, 기존 저장소는 HTTPS로 접속하는 Git 호스트라면 어디서든 가져올 수 있습니다. OwnGit 소스는 MIT 라이선스로 공개되어 있습니다.
강제 푸시나 브랜치 삭제를 되돌려야 합니다. 코딩 에이전트가 한 일도 마찬가지입니다.
보관된 기록이 있습니다. 푸시로 덮어쓰거나 지운 브랜치와 태그를 OwnGit이 보관합니다. 브라우저에서 바뀔 내용을 모두 확인한 뒤 트리 전체나 고른 파일만 되돌립니다.
필요한 날 제대로 복원되는 백업이 있어야 합니다.
오프라인 백업이 있습니다. owngit backup은 ref와 객체, 풀 리퀘스트와 체크, 가져오기 기록, 옮길 수 있는 설정을 SHA-256 해시와 함께 복사합니다. owngit restore는 백업을 새 설치로 되살립니다.
데이터베이스가 딸린 무거운 서버를 또 관리하고 싶지 않습니다.
프로그램 하나에 45~70 MB 정도입니다. OwnGit은 Go 실행 파일 하나와 SQLite 파일로 돌아갑니다. 따로 띄울 데이터베이스 서비스가 없고, 실행할 때 Node나 Python도 필요 없습니다. 대기 중 메모리는 45 MB 정도이고 저장소가 1,000개면 70 MB 정도입니다.
스팸 가입을 치우는 데 지쳤습니다.
가입도 계정도 없습니다. OwnGit에는 가입 화면도 사용자 계정도 없어서 봇이 등록할 곳이 없습니다. 저장소는 누구나 쓰게 열어 두거나 공용 비밀번호 하나로 막고, 보안 설정은 별도의 관리자 비밀번호로 보호합니다.
포트를 열지 않고 노트북에서 접속하고 싶습니다.
tailnet에서 HTTPS로 엽니다. OwnGit은 기본적으로 자기 컴퓨터에서 오는 연결만 받습니다. 설정 화면이나 owngit tailscale on으로 Tailscale Serve를 켜거나, 쓰던 리버스 프록시를 앞에 두면 됩니다.
코딩 에이전트가 정확하게 호출할 수 있는 도구가 필요합니다.
owngit mcp와 JSON을 출력하는 명령이 있습니다. MCP 도구는 저장소, 풀 리퀘스트, diff, 체크를 다룹니다. 도구마다 무엇을 바꾸는지 밝히고 같은 이름의 명령과 똑같은 JSON을 돌려주므로, 셸을 쓰는 에이전트는 owngit pr diff를 바로 실행하면 됩니다.
푸시가 브랜치를 덮어쓰거나 누가 브랜치를 지우면 OwnGit은 이전 커밋을 보관된 기록에 남깁니다. 브라우저에서 저장소를 열고, 되돌리면 바뀔 내용을 모두 확인한 다음 트리 전체나 고른 파일만 되돌리면 됩니다. 코딩 에이전트가 한 푸시도 같은 방법으로 되돌립니다.
보관된 기록은 백업이 아닙니다. 실수로 푸시한 비밀값도 함께 남으므로, 유출된 비밀값은 강제 푸시로 지우려 하지 말고 새로 바꾸세요.
OwnGit을 멈추고 명령 하나를 실행하세요. 백업에는 보관된 기록을 포함한 모든 ref와 객체, 풀 리퀘스트와 체크, 가져오기 기록, 옮길 수 있는 설정이 들어가고, 손상을 알아낼 SHA-256 해시도 함께 저장됩니다. owngit restore는 백업을 새 상태 디렉터리에 되살립니다.
$ owngit backup --output ~/Backups/owngit-backup Offline backup written to ~/Backups/owngit-backup. SHA-256 hashes detect corruption but do not authenticate a replaced backup. $ owngit restore --input ~/Backups/owngit-backup --state-dir ~/restored- state --repository-root ~/restored-repositories Offline backup restored to ~/restored-state with repositories at ~/resto red-repositories. Previous sessions, trusted hosts, and network settings were not restored.
OwnGit은 30 MB 정도의 Go 실행 파일 하나와 SQLite 파일로 돌아가며, 따로 띄울 데이터베이스 서비스가 없습니다. 이 수치는 Linux x64, Raspberry Pi 5, Windows 11, macOS에서 측정했습니다.
대기 중 메모리, 저장소 없음
Linux x64, Raspberry Pi 5, Windows 11
대기 중 메모리, 저장소 1,000개
저장소 100개일 때 50 MB 정도. macOS는 footprint 기준 67 MB
시작부터 첫 화면까지
Linux x64에서 새로 시작할 때의 중앙값. Raspberry Pi 5는 0.2초
대기 중 CPU(코어 하나 기준), 저장소 1,000개
측정값 0.15~0.64%. 저장소가 100개 이하면 대체로 0.1% 이하
OwnGit 1.1.0을 설정한 뒤 아무도 쓰지 않는 상태에서 메모리가 안정되었을 때 상주 메모리(RSS 또는 working set)를 쟀습니다. 클론이나 푸시를 하면 Git이 따로 실행되며, 그 메모리는 저장소에 따라 다릅니다.
| 서버 | 대기 중, 저장소 없음 | 대기 중, 저장소 100개 |
|---|---|---|
| A | 171.0 MB | 178.0 MB |
| B | 190.9 MB | 196.4 MB |
| OwnGit 1.1.0 | 45.4 MB | 50.7 MB |
세 서버 모두 같은 Debian 13 컨테이너(코어 2개, 메모리 2 GB)에 SQLite로 기본 설치하고, 6분 동안 안정시킨 뒤 서버 프로세스의 RSS를 쟀습니다. A와 B는 이슈 트래커, 위키, 패키지, CI처럼 기본으로 하는 일이 훨씬 많으므로 같은 일을 하는 조건이 아니라 기본 설치끼리 비교한 값입니다.
OwnGit에는 가입 화면도 사용자 계정도 없습니다. 저장소는 네트워크 안에서 누구나 쓰게 열어 두거나 공용 비밀번호 하나로 막고, 보안 설정은 별도의 관리자 비밀번호로 보호합니다.
노트북이나 휴대폰에서 쓰려면 설정 화면에서 공유를 켜거나 owngit tailscale on을 실행하세요. 그러면 OwnGit이 tailnet에 HTTPS로 열립니다. 켜기 전에 OwnGit은 컴퓨터와 tailnet 이름이 공개 인증서 로그에 남는다는 점을 알려 줍니다. 리버스 프록시도 쓸 수 있으며, 운영 안내에 Caddy, nginx, Traefik 예시가 있습니다.
$ owngit network show Saved settings (used at the next start; a serve flag overrides one for t hat run): Listen address: 127.0.0.1:18432 Base URL: not saved, addresses follow the address each client us es Allowed Hosts: none localhost, 127.0.0.1 and ::1 are always accepted. Trusted proxies: none Running server (process 70198, started 2026-09-26T14:43:26Z): Listen address: 127.0.0.1:18432 (saved), listening on 127.0.0.1:18432 Base URL: none, addresses follow the address each client uses (d efault) Accepted Hosts: 127.0.0.1, ::1, localhost Trusted proxies: none (default) The running server uses the saved settings. $ owngit tailscale status Sharing on the tailnet over HTTPS: off. Tailscale: ~/demo/bin/tailscale Ready to share as https://sample-host.example-tailnet.ts.net/. Turn it on with "owngit tailscale on", or in Settings. $ owngit tailscale on When Tailscale issues the certificate for this address, the names of thi s computer and your tailnet, as in sample-host.example-tailnet.ts.net, a re recorded in a public certificate log. Only the fact that the address was opened is recorded, not your code, repositories, passwords or other content. To use another name, change it in the Tailscale admin console b efore turning sharing on. Names that already got a certificate stay in t he log, even after a rename. Tailscale now answers HTTPS for sample-host.example-tailnet.ts.net and p asses it to OwnGit at http://127.0.0.1:18432. Saved: base URL https://sample-host.example-tailnet.ts.net, allowed name sample-host.example-tailnet.ts.net, trusted proxy 127.0.0.1. Sharing on the tailnet over HTTPS: on, but not ready yet. Address: https://sample-host.example-tailnet.ts.net/ (encrypted by T ailscale on this computer) Clone URL: https://sample-host.example-tailnet.ts.net/git/REPOSITORY.g it Tailscale: ~/demo/bin/tailscale Restart OwnGit to finish. The running server does not use the new sett ings yet. This command cannot change a running OwnGit, so the change applies after a restart. Turning sharing on or off in Settings applies at once.
풀 리퀘스트를 열고 바뀐 내용을 읽은 뒤, 확인한 커밋 그대로 병합합니다. 대시보드와 owngit pr는 같은 데이터로 같은 단계를 거칩니다. 리뷰는 선택입니다.
내 컴퓨터에서 실행한 체크는 검사한 커밋에 묶여 기록됩니다. 체크 결과는 참고용이며 병합을 막지 않습니다.
$ owngit pr create --source feature/sitemap --target main --title "Add s itemap" --accept-insecure-http | jq '.pull_request | {number, title, sta te}' owngit: using server http://127.0.0.1:18432 and repository website from the origin remote { "number": 1, "title": "Add sitemap", "state": "open" } $ owngit pr diff --number 1 --stat --accept-insecure-http | jq -c '.sour ce, .target, .files[]' owngit: using server http://127.0.0.1:18432 and repository website from the origin remote {"branch":"feature/sitemap","oid":"c89e0e2aefe2cfcc798829cbc10bcdb38bda1 574"} {"branch":"main","oid":"f744962ea89f44bf26b7342f9dc271f33acee085"} {"path":"sitemap.xml","status":"added","additions":2,"deletions":0,"bina ry":false} $ owngit pr merge --number 1 --source-oid c89e0e2aefe2cfcc798829cbc10bcd b38bda1574 --target-oid f744962ea89f44bf26b7342f9dc271f33acee085 --accep t-insecure-http | jq -c '.pull_request | {state, merge: .merge.mode}' owngit: using server http://127.0.0.1:18432 and repository website from the origin remote {"state":"merged","merge":"fast_forward"}
owngit mcp는 표준 입출력으로 통신하는 로컬 MCP 서버이며 네트워크 포트를 열지 않습니다. 이 도구로 저장소, 풀 리퀘스트, diff, 체크를 읽고, 풀 리퀘스트를 만들고 리뷰하며, 클론에 커밋된 체크를 실행합니다. 결과는 같은 이름의 명령과 똑같은 JSON입니다. 클론 안에서는 owngit pr와 owngit repo가 origin에서 서버 주소를 찾습니다.
▐▛███▛█ Claude Code v2.1.283 ▝▜██████▀ Sonnet 5 · Claude Max ▝▝ ▝▝ ~/project ❯ List the repositories on OwnGit. Open a pull request from feature/sitemap into main titled "Add sitemap" and show its diff stat. Create a check task and run the checks. If they pass, approve the pull request as reviewer "claude-code" and merge it. Reply in two short sentences. Called owngit 3 times (ctrl+o to expand) ⏺ PR #1 diff stat: .owngit/checks.json (+12), sitemap.xml (+2) — 2 files, 14 insertions. Called owngit 2 times (ctrl+o to expand) ⏺ Checks passed (clean worktree, tested commit e0e3ec7). Proceeding to approve and merge. Called owngit 2 times (ctrl+o to expand) ⏺ OwnGit hosts one repository, "website"; PR #1 "Add sitemap" changed 2 files (+14 lines), passed its check (xmllint --noout sitemap.xml), was approved by "claude-code", and merged into main via fast-forward. ✻ Cogitated for 53s · done 2:46 PM ──────────────────────────────────────────────────────────────────────────────────────────────────── ❯ delete the feature/sitemap branch ──────────────────────────────────────────────────────────────────────────────────────────────────── ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents
$ cat ~/.codex/config.toml [mcp_servers.owngit] command = "owngit" args = ["mcp", "--workdir", "~/website", "--credential-file", "~/helper- token", "--accept-insecure-http"] tool_timeout_sec = 1800 $ codex exec --ephemeral --skip-git-repo-check -m gpt-5.6-luna -c 'model _reasoning_effort="low"' -c 'mcp_servers.owngit.default_tools_approval_m ode="approve"' 'Use the owngit tools. List the repositories. Open a pull request from feature/sitemap into main titled "Add sitemap" and show it s diff stat. Create a check task and run the checks. If they pass, appro ve the pull request with the reviewer label "codex" and merge it. Answer in English in two short sentences.' OpenAI Codex v0.157.0 -------- workdir: ~/project model: gpt-5.6-luna provider: openai approval: never sandbox: read-only reasoning effort: low reasoning summaries: none session id: 01a0dbaf-11a2-7d83-b1e8-42cccbdf4ef6 -------- user Use the owngit tools. List the repositories. Open a pull request from fe ature/sitemap into main titled "Add sitemap" and show its diff stat. Cre ate a check task and run the checks. If they pass, approve the pull requ est with the reviewer label "codex" and merge it. Answer in English in t wo short sentences. codex I’m using the OwnGit repository tools to list repositories, open the pul l request, run its checks, and merge it if everything passes. mcp: owngit/repository_list started mcp: owngit/repository_list (completed) mcp: owngit/pull_request_create started mcp: owngit/pull_request_create (completed) mcp: owngit/check_task_create started mcp: owngit/check_task_create (completed) mcp: owngit/pull_request_diff started mcp: owngit/pull_request_diff (completed) mcp: owngit/check_run started mcp: owngit/check_run (completed) mcp: owngit/pull_request_review started mcp: owngit/pull_request_review (completed) mcp: owngit/pull_request_merge started mcp: owngit/pull_request_merge (completed) codex Listed the `website` repository; PR #1 “Add sitemap” has a 2-file, 14-li ne diff. Checks passed, it was approved by `codex`, and merged into `mai n`. tokens used 20,918
Took 0.0s Now run the checks with the task id. owngit_check_run { "task": "7658d0a0c6ddf98fead860d5ad… → {"ok":true,"register… … (Ctrl+O to expand) Checks passed. Now approve as reviewer "pi" and merge. I need source_oid and target_oid from the PR show. Checks passed. I'll approve the pull request as reviewer "pi" and merge it. owngit_pull_request_review { "number": 1, "source_oid"… → {"ok":true,"pull_req… … (Ctrl+O to expand) Now merge. owngit_pull_request_merge { "number": 1, "source_oid":… → {"ok":true,"pull_req… … (Ctrl+O to expand) The user asked for two short sentences. I need to include: OwnGit has one repository, website, and pull request #1 “Add sitemap” changes .owngit/checks.json (+12) and sitemap.xml (+2), 14 insertions total. The sitemap check passed, so I approved it as pi and fast-forward merged it into main. ──────────────────────────────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────────────────────────────── ~/project (main) ↑26k ↓1.4k R52k CH93.5% $0.087 (sub) 2.5%/500k (auto) (xai) grok-4.7 • medium 🔌 MCP: 1 server enabled (1 connected)
어떤 방법이든 호스트에 git-http-backend가 포함된 Git이 있어야 합니다. Homebrew와 Arch 패키지는 Git을 함께 설치합니다.
brew install juliankang4/tap/owngit
macOS(Apple silicon)와 Linux(x64, ARM64)에서 쓸 수 있습니다.
npm install -g owngit
macOS(Apple silicon), Linux(x64, ARM64), Windows(x64)에서 쓸 수 있습니다. 설치할 때와 OwnGit을 시작할 때 Node.js가 필요합니다.
curl -fLO https://github.com/juliankang4/owngit/releases/latest/download/PKGBUILD && makepkg -si
릴리스마다 첨부된 PKGBUILD로 패키지를 만듭니다. makepkg가 압축 파일의 SHA-256을 확인한 뒤 pacman으로 설치합니다.
GitHub Releases에서 플랫폼에 맞는 압축 파일을 내려받아 SHA256SUMS로 확인하세요. 실행 파일에는 서명이 없습니다.
터미널에서 OwnGit을 처음 시작하면 그 자리에서 설정이 시작됩니다. 언어를 고른 뒤 터미널이나 브라우저에서 이어 가면 됩니다. 그다음부터 저장소는 대시보드나 owngit repo로 만듭니다.
$ owngit serve 2026/09/26 14:44:44 using git version 2.54.0 (Apple Git-157) at /Applica tions/Xcode.app/Contents/Developer/usr/libexec/git-core/git (found on PA TH; the Git behind the macOS /usr/bin/git shim, same version) 2026/09/26 14:44:44 OwnGit listening on 127.0.0.1:18432 ___ ___ _ _ / _ \ __ __ __ _ _ / __|(_)| |_ | (_) |\ V V / | ' \ | (_ || || _| \___/ \_/\_/ |_||_| \___||_| \__| 처음 설정 http://127.0.0.1:18432/ +- Language / 언어 ---------------------------------------------------- | [1] English | [2] 한국어 | | Press Enter for the default. You can switch later with L. | Enter만 누르면 기본값을 씁니다. 나중에 L 키로 바꿀 수 있습니다. +---------------------------------------------------------------------- Choice / 선택 [2]: 2 +- OwnGit 설치 -------------------------------------------------------- | OwnGit은 실행 중이지만 아직 설정되지 않았습니다. 설치 질문에 | 어디서 답할지 고르세요. | | [1] 이 터미널에서 계속 | 여기서 몇 가지 질문에 답합니다. 비밀번호는 입력해도 화면에 | 나타나지 않습니다. | | [2] 웹 대시보드 열기 | 이 터미널에서 브라우저를 승인한 뒤 브라우저에서 설치를 이어 | 갑니다. | | [L] English +---------------------------------------------------------------------- 선택 [1]: 1 +- 저장소 폴더 ------------------------------------------------- 1/5 -- | OwnGit은 이 폴더 안에 저장소를 만들고, 폴더에 있던 파일은 그대로 | 둡니다. | | 전체 경로를 입력하세요. Enter만 누르면 제안한 폴더를 씁니다. +---------------------------------------------------------------------- 제안: ~/OwnGit- Repositories 폴더: [ok] 이 폴더를 쓸 수 있습니다. +- 저장소를 읽고 쓸 수 있는 사람 ------------------------------- 2/5 -- | 네트워크 안에서의 평소 저장소 사용에 적용됩니다. 보안 설정은 | 언제나 관리자 비밀번호가 필요합니다. | | [1] 이 네트워크의 모든 사람 | 읽기와 푸시에 비밀번호가 없습니다. 직접 관리하는 사설 LAN이나 | Tailscale 네트워크에 적합합니다. | | [2] 공용 비밀번호를 아는 사람 | 모두가 같은 비밀번호 하나를 사용합니다. 개인 계정은 없습니다. | | [L] English +---------------------------------------------------------------------- 선택 [1]: 1 +- 관리자 비밀번호 --------------------------------------------- 3/5 -- | 어느 브라우저에서든 보안 설정을 바꿀 때 필요합니다. 공용 | 비밀번호와는 다르게 정하세요. | | 8자 이상 1024자 이하로 입력하세요. 입력한 글자는 화면에 나타나지 | 않습니다. +---------------------------------------------------------------------- 관리자 비밀번호: 한 번 더 입력: [ok] 관리자 비밀번호를 입력했습니다. +- 다른 기기에서 접속 ------------------------------------------ 4/5 -- | [ok] 이 컴퓨터에서 Tailscale이 실행 중입니다. | | Tailscale 주소 100.64.0.10 | MagicDNS 이름 sample-host.example-tailnet.ts.net | | 다른 Tailscale 기기에서 OwnGit을 쓰려면 아래 명령으로 네트워크 | 설정을 저장하고, 설치를 마친 뒤 OwnGit을 다시 시작하세요. 나중에 | 설정 화면의 "네트워크"에서 바꿀 수도 있습니다. | | Tailscale이 기기 사이의 연결을 암호화하지만, OwnGit은 그 보호를 | 확인할 수 없어 계속 일반 HTTP로 표시합니다. +---------------------------------------------------------------------- 설정을 저장하는 명령 (한 줄 전체를 복사하세요): owngit network set --listen 100.64.0.10:18432 --base-url http://sample-h ost.example-tailnet.ts.net:18432 OwnGit을 백그라운드 서비스로 실행해도 저장된 설정이 적용됩니다. 서비스 정의에 --listen이나 --base-url 옵션이 있으면 저장된 값 대신 옵션 값을 쓰므로 빼 두세요. OwnGit 문서의 "백그라운드 서비스의 옵션"을 보세요. 계속하려면 Enter를 누르세요: +- 설정 확인 --------------------------------------------------- 5/5 -- | 설치 완료를 고르기 전에는 아무것도 저장하지 않습니다. | | 저장소 폴더 ~/OwnGit-Repositories | 접근 이 네트워크의 모든 사람 | 관리자 비밀번호 입력함 | 연결 이 컴퓨터에서만 | | [1] 설치 완료 | [2] 처음부터 다시 | | [L] English +---------------------------------------------------------------------- 선택 [1]: 1 +- [ok] 설치를 마쳤습니다 --------------------------------------------- | 대시보드 http://127.0.0.1:18432/ | | 준비되면 첫 저장소를 만드세요. +---------------------------------------------------------------------- OwnGit은 계속 실행됩니다. 아래로 서버 로그가 이어집니다. 2026/09/26 14:44:47 setup completed 2026/09/26 14:44:47 OwnGit ready at http://127.0.0.1:18432/
$ owngit repo create --name website --description "Sample website" \ > --server http://127.0.0.1:18432 --accept-insecure-http \ > | jq .repository { "id": "website", "name": "website", "description": "Sample website", "created_at": "2026-09-26T14:44:47Z", "clone_url": "http://127.0.0.1:18432/git/website.git" } $ owngit repo list --server http://127.0.0.1:18432 \ > --accept-insecure-http | jq -c '.repositories[] | {name, clone_url}' {"name":"website","clone_url":"http://127.0.0.1:18432/git/website.git"}