Henry
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags: linux2022 --- # 2022q1 Homework (quiz2) contributed by <`ibat10clw`> > [quiz2](https://hackmd.io/@sysprog/linux2022-quiz2#2022q1-%E7%AC%AC-2-%E9%80%B1%E6%B8%AC%E9%A9%97%E9%A1%8C) ## 測驗1 ### 程式碼運作原理 ```c uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (EXP1); } ``` 觀察上述程式碼, ` a >> 1 ` 和 ` b >> 1` 可視為除以 2 的運算, 但當 a 以及 b 為奇數時回傳值會與正確結果差 1 因此 EXP1 需要在 a 和 b 都是奇數的情況下為 1, 其餘情況為 0 在二進位下判斷奇偶的方法可以透過確認 LSB 來做判斷, 若 LSB = 1 為奇數, LSB = 0 為偶數 所以可以將 a 和 b 與 1 做 bitwise and 來確認 LSB 的結果 * `EXP1 = a & b & 1` ```c uint32_t average(uint32_t a, uint32_t b) { return (EXP2) + ((EXP3) >> 1); } ``` 從加法器的角度切入 sum 和 carry 分別能以下列操作表示: * sum: `x ^ y` * carry: `(x & y) << 1` 把上面兩者的結果相加等同於 (x + y), 回到題目部份剛好也是兩個運算式相加, 但其中有一個 right shift, 可判斷為 (sum + carry) >> 1 後變成的結果 嘗試將 sum 和 carry 相加後做 right shift * `(((x ^ y) + (x & y) << 1)) >> 1` `= (((x ^ y) >> 1) + (x & y))` `= (x & y) + ((x ^ y) >> 1)` 最後便可推得下列結果 * `EXP2 = (x & y), EXP3 = (x ^ y)` ### 觀察編譯器行為 ```shell 0000000000000000 <average>: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 89 7d fc mov %edi,-0x4(%rbp) b: 89 75 f8 mov %esi,-0x8(%rbp) e: 8b 45 fc mov -0x4(%rbp),%eax 11: 23 45 f8 and -0x8(%rbp),%eax 14: 89 c2 mov %eax,%edx 16: 8b 45 fc mov -0x4(%rbp),%eax 19: 33 45 f8 xor -0x8(%rbp),%eax 1c: d1 e8 shr %eax 1e: 01 d0 add %edx,%eax 20: 5d pop %rbp 21: c3 retq ``` * 上面是 -O0 產生的組合語言程式碼 * 組合語言指令解析: * `push src:` 將 src 的資料 push 進 stack * `mov dest src:` 將 src 的資料搬到 dest * `xor dest src:` 將 dest 與 src 做 XOR 後存回 dest * `add dest src:` 將 dest 與 src 做 ADD 後存回 dest * `shr:` shift right * `pop dest:` 將 stack 的資料 pop 給 dest ```shell 0000000000000000 <average>: 0: f3 0f 1e fa endbr64 4: 89 f8 mov %edi,%eax 6: 31 f0 xor %esi,%eax 8: d1 e8 shr %eax a: 21 f7 and %esi,%edi c: 01 f8 add %edi,%eax e: c3 retq ``` * 上面是 -O1 產生的組合語言程式碼 * 與 -O0 最大的差別是少了 stack 相關的操作 ```shell 0000000000000000 <average>: 0: f3 0f 1e fa endbr64 4: 89 f8 mov %edi,%eax 6: 21 f7 and %esi,%edi 8: 31 f0 xor %esi,%eax a: d1 e8 shr %eax c: 01 f8 add %edi,%eax e: c3 retq ``` * 上面是 -O2 和 -O3 產生的組合語言程式碼 * 與 -O1 的差別運算順序有些許不同 ## 測驗2 ### 程式碼運作原理 ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((EXP4) & -(EXP5)); } ``` * 解題靈感來自於 [LeetCode 136. single number](https://leetcode.com/problems/single-number/), 透過 XOR 能夠找出 single number 的特性來做思考 * 由於程式碼左半邊的 `a ^` 的部份以固定, 因此想令括號內的結果等價為下列情形: * `a ^ b`: 當 b 比較大的時候, `a ^ a ^ b` 能夠將 a 消掉, 正好將 b 作為回傳值 * `a ^ a`: 當 a 比較大的時候, `a ^ a ^ a` 由於 a ^ a 的結果是 0, a ^ 0 後又會恢復為 a, 將 a 回傳正好也是最大值 1. 首先根據提示 EXP5 為一個 logical 的運算, 根據 C 語言規格, 邏輯運算結果只會是 0 或 1, 在灌上一個負號後的最後能產生的結果只會是 `0xffffffff` 或 `0x00000000` 1. 再來考慮 & 的特性 * `x & 1 = x`: 不變 * `x & 0 = 0`: 強制歸零 可以根據上述兩種可能的結果繼續推測 EXP4 的可能 1. 回到第一點的討論, EXP4 根據提示為 bitwise 的操作, 而我們希望能夠產出一個 XOR 的結果, 因此這邊填入 `a ^ b` 1. 由於 EXP4 也已經做選擇, 根據 & 運算性質的討論可得最後結論: * `a ^ ((a ^ b) & -(1))`: 由於 `a ^ b` 不變最後結果剩下 b * `a ^ ((a ^ b) & -(0))`: 由於括號內被歸零最後結果剩下 a 也就是當實際狀況 a > b 時, 我們希望 EXP5 的結果為 false, 最後結果才能將 a 留下, 因此 EXP5 可填入 `a < b` 同樣的驗證實際情況為 a < b 時, 因 EXP5 的結果為 true, 括號內不變而兩個 a 抵銷後最後留下了 b 整理上述的原理後可得下列結果 * `EXP4 = a ^ b`, `EXP5 = a > b` ## 測驗3 ### 程式碼運作原理 ```c= #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } while (!(u & 1)) u /= 2; do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (COND); return RET; } ``` 以上的程式碼其實就是老師測驗題上提供的演算法的實作 * 程式的最一開始先判斷特例情況, 若有任一數字為 0 則可以直接得到 GCD * 當兩個數字都為偶數的情況下, $GCD(u, v)$ 可化簡為 $2 * GCD( \frac{u}{2} , \frac{v}{2})$, 第 6 行的迴圈便是為了簡化而做紀錄, u 和 v 每除了一次 2, 最後就要乘回來一次, 把次數紀錄在變數 shift * 根據演算法提示, 只要 u 和 v 為一奇一偶的情況則可以進一步化簡 * if u is odd and v is even, then $GCD(u, v) = GCD(u, \frac{v}{2})$ * else if u is even and v is odd, then $GCD(u, v) = GCD(\frac{u}{2}, v)$ * 第 9 和 12 行的 while 是在找尋進一步化簡的可能 * 14 至 19 行則是在實現減法版本的 Euclidean algorithm, 並在每次 do-wile 的開頭嘗試化簡的可能 * if u < v, then $GCD(u, v) = GCD(u, v - u)$ * else $GCD(u, v) = GCD(v, u - v)$ * `COND = u != 1 && v` 根據 Euclidean algorithm 的終止條件而設立 * 其中 `u != 1` 是因為當 `u == 1` 時 u 的值就不會在改變了 * `RET = u << shift` 根據前面推導若在 for 迴圈的階段有做化簡則最後要乘回來 ## 測驗4 ### 程式碼運作原理 ```c= #include <stddef.h> size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; for (size_t k = 0; k < bitmapsize; ++k) { uint64_t bitset = bitmap[k]; size_t p = k * 64; for (int i = 0; i < 64; i++) { if ((bitset >> i) & 0x1) out[pos++] = p + i; } } return pos; } ``` 上面的程式碼能夠找出在二進位表示中 bitset 的 bit 為 1 的位置, 並且將其紀錄在 out 裡面, 最後能夠根據回傳的 pos 在 out 中取出有效的紀錄 ```c size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; uint64_t bitset; for (size_t k = 0; k < bitmapsize; ++k) { bitset = bitmap[k]; while (bitset != 0) { uint64_t t = EXP6; int r = __builtin_ctzll(bitset); out[pos++] = k * 64 + r; bitset ^= t; } } return pos; } ``` * 使用 __builtin_ctzll 改寫後的結果如上, 只要當前的 bitset 不為 0, 就透過 __builtin_ctzll 從右往左找出第一個 bit 為 1 的位置 * 透過 left shift 將 1 移動到 __builtin_ctzll 回傳的位置, 最後在根據 XOR 的特性將對應位置的 bit 化為0, 而其他 bit 不變, 直到 bitset 歸零為止 綜合上述整理可得結果 * `EXP6 = 0b1L << __builtin_ctzll(bitset)` ## 測驗5 ### 程式碼運作原理 ```c= struct rem_node { int key; int index; struct list_head link; }; static int find(struct list_head *heads, int size, int key) { struct rem_node *node; int hash = key % size; list_for_each_entry (node, &heads[hash], link) { if (key == node->key) return node->index; } return -1; } char *fractionToDecimal(int numerator, int denominator) { int size = 1024; char *result = malloc(size); char *p = result; if (denominator == 0) { result[0] = '\0'; return result; } if (numerator == 0) { result[0] = '0'; result[1] = '\0'; return result; } long long n = numerator; long long d = denominator; if (n < 0) n = -n; if (d < 0) d = -d; bool sign = (float) numerator / denominator >= 0; if (!sign) *p++ = '-'; long long remainder = n % d; long long division = n / d; sprintf(p, "%ld", division > 0 ? (long) division : (long) -division); if (remainder == 0) return result; p = result + strlen(result); *p++ = '.'; char *decimal = malloc(size); memset(decimal, 0, size); char *q = decimal; size = 1333; struct list_head *heads = malloc(size * sizeof(*heads)); for (int i = 0; i < size; i++) INIT_LIST_HEAD(&heads[i]); for (int i = 0; remainder; i++) { int pos = find(heads, size, remainder); if (pos >= 0) { while (PPP > 0) *p++ = *decimal++; *p++ = '('; while (*decimal != '\0') *p++ = *decimal++; *p++ = ')'; *p = '\0'; return result; } struct rem_node *node = malloc(sizeof(*node)); node->key = remainder; node->index = i; MMM(&node->link, &heads[EEE]); *q++ = (remainder * 10) / d + '0'; remainder = (remainder * 10) % d; } strcpy(p, decimal); return result; } ``` * 程式的計算方法是模擬手算長除法時的情況 * 其中 24 ~ 33 行的部份為處理除數或被除數為零的特例, 可以直接得到結果 * 43 ~ 44 行透過將兩數相除判斷是否需要在最後結果加上負號 * 47 ~ 52 行進行了第一輪計算, 並透過 sprintf 將結果 int 的資料轉為 string 放入 result 中, 同時檢查 remainder 是否為 0, 若可以整除的話可以直接得出結果 * 54 ~ 55行透過 result 的整數部份找出放置 `'.'` 的位置後將其置入 * 57 ~ 64 行在為了後續計算建立一個 hash table, 其中 size 為 bucket 的數量 * 66行開始為長除法的計算, 長除法中判斷循環小數的方式為出現了重複的餘數 每次 for 迴圈的開始都會透過 hash 回傳的 pos 確認是否餘數已經出現過了 * 當 `pos >= 0` : 代表已能確認循環的部份, 透過回傳的 pos 先將 decimal 不循環的部份複製到 p, 之後擺上 `'('` , 把 decimal 剩下的部份還有 `')'` 跟 null terminator 放入後回傳結果 * 當 `pos == -1` : 代表尚未發現循環的部份, 因此將該輪的結果放入 hash list 中 * 84 ~ 85 行為長除法每次都需要將數字最後補零的操作 * 88 ~ 89 若能成功將餘數清為零, 則將 decimal 整個複製給 p, 最後將結果回傳 綜合上述整理可得結果 * `PPP = pos--` * PPP 會影響擺放 `'('` 的位置, 這取決於回傳的 pos, 而 pos 又紀錄了該個 remainder 是第幾 `n` 計算的結果, 因此需要在小數後 `n` 位補上 `'('` * `MMM = list_add, EEE = size % remainder` * MMM 和 EEE 為在 hash list 中新增資料的方式, 由於 hash function 為計算傳入參數 `key % size` 的結果, 而在程式碼 67 行傳入的各是 size 和 remainder, 因此加入時同樣加入在 `size % remainder` 個 bucket, 另外由於是 circular doubly linked list, 因此加在尾巴或頭都可以 ### 可改進之處 TODO ## 測驗6 ### 程式碼運作原理 ```c /* * ALIGNOF - get the alignment of a type * @t: the type to test * * This returns a safe alignment for the given type. */ #define ALIGNOF(t) \ ((char *)(&((struct { char c; t _h; } *)0)->M) - (char *)X) ``` * `__alignof__` 計算的是若想對齊某資料需要花的 byte 數量 * 根據 macro 定義傳入的參數 `t` 以及 `struct` 內會將 `t` 代換掉並同時以 `t` 的資料型態宣告變數 `_h` * 同時使用一個 literal `0` 來當載體並且計算 `_h` 與這個 `struct` 自身的差距有多少作為對齊所需的 byte 數 根據上述整理可得結果 * `M = _h` * 因為想計算的是 `t->_h` 與 `t` 的差距, 因此填入 `_h` * `X = 0` * 由於前面是用 `0` 來當載體, 後面也要一致 ## 測驗7 ### 程式碼運作原理 ```c= static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; int main(int argc, char **argv) { for (size_t i = 1; i <= 100; i++) { uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); unsigned int length = (2 << KK1) << KK2; char fmt[9]; strncpy(fmt, &"FizzBuzz%u"[(8 >> div5) >> (KK3)], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ``` 首先看過整個主程式的執行流程 * 12 ~ 13 行的 is_divisible 用來測試是 i 否為 3 或 5 的倍數, 之後根據測試結果來設定 length * is_divisible 回傳的值為 0 或 1, 因此 14 行的操作也能看為根據測試結果將 length 乘以 2 * 17 行的部份透過 div5 和 div3 的結果取出 `"FizzBuzz%u"` 中的一部分並將複製到 fmt 中 * 剩下的部份為加入 NULL terminator 以及將其印出 可以發現關鍵在於: * is_divisible 的實作 * 如何透過 length, div3, div5 取出 `"FizzBuzz%u"` 的片段 #### is_divisible 在閱讀了 [Faster remainders when the divisor is a constant: beating compilers and libdivide](https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/) 網頁中提出的方法後得知若想計算除法可以透過乘法的模擬得出 * $\Large \frac{n}{d} = n * \frac{2^N}{d} * \frac{1}{2^N}$ * 其中 $\Large \frac{2^N}{d}$ 是需要預計算的部份, $\Large \frac{1}{2^N}$ 可以透過 shift 得出, 因此往後我們便能只透過乘法來得出除法的結果 * 餘數的部份可從上述公式中的 LSB 乘上 d 後做 right shift 獲得 #### 控制 `"FizzBuzz%u"` 的片段 由於 `"FizzBuzz%u"` 會需要印出的片段分別為: * Fizz: 位移量 0, 長度 4 * Buzz: 位移量 4, 長度 4 * `%u`: 位移量 8, 長度 2 * FizzBuzz: 位移量0, 長度8 需要控制的變數皆為 powers of 2, 因此可以用 8 為底搭配 right shift 的操作來取出所需片段 綜合上述討論可得: * `KK1 = div3, KK2 = div5` * length 是以 2 為底來做 shift 的, 而當 div3 或 div5 的值為 1 時便可以透過 left shift 變為 4 或 8 * `KK3 = div3 << 2` * 題目已知的部份有 `(8 >> div5) >>` 先討論 div5 為 1 的情況, 結果會變成 `4 >> (KK3)`, 而這時 div3 還沒有用上, 且希望透過 div3 來控制要印出 `FizzBuzz` 或是 `Buzz` * 這兩者的位移量分別是 0 以及 4, 所以 `KK3` 需要在真的時候產出 2 ,假的時候產出 0 的結果, 以便讓`(8 >> div5) >> (KK3)`能夠符合預期行為 * 因此 KK3 填入 `div3 << 2`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully