file_history_walk.cc 10.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
NAN_METHOD(GitRevwalk::FileHistoryWalk)
{
  if (info.Length() == 0 || !info[0]->IsString()) {
    return Nan::ThrowError("File path to get the history is required.");
  }

  if (info.Length() == 1 || !info[1]->IsNumber()) {
    return Nan::ThrowError("Max count is required and must be a number.");
  }

  if (info.Length() == 2 || !info[2]->IsFunction()) {
    return Nan::ThrowError("Callback is required and must be a Function.");
  }

  FileHistoryWalkBaton* baton = new FileHistoryWalkBaton;

  baton->error_code = GIT_OK;
  baton->error = NULL;
  String::Utf8Value from_js_file_path(info[0]->ToString());
  baton->file_path = strdup(*from_js_file_path);
  baton->max_count = (unsigned int)info[1]->ToNumber()->Value();
Tyler Wanek's avatar
Tyler Wanek committed
22
  baton->out = new std::vector< std::pair<git_commit *, std::pair<char *, git_delta_t> > *>;
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
  baton->out->reserve(baton->max_count);
  baton->walk = Nan::ObjectWrap::Unwrap<GitRevwalk>(info.This())->GetValue();

  Nan::Callback *callback = new Nan::Callback(Local<Function>::Cast(info[2]));
  FileHistoryWalkWorker *worker = new FileHistoryWalkWorker(baton, callback);
  worker->SaveToPersistent("fileHistoryWalk", info.This());

  Nan::AsyncQueueWorker(worker);
  return;
}

void GitRevwalk::FileHistoryWalkWorker::Execute()
{
  git_repository *repo = git_revwalk_repository(baton->walk);
  git_oid *nextOid = (git_oid *)malloc(sizeof(git_oid));
  giterr_clear();
  for (
    unsigned int i = 0;
    i < baton->max_count && (baton->error_code = git_revwalk_next(nextOid, baton->walk)) == GIT_OK;
    ++i
  ) {
    // check if this commit has the file
    git_commit *nextCommit;

    if ((baton->error_code = git_commit_lookup(&nextCommit, repo, nextOid)) != GIT_OK) {
      break;
    }

    git_tree *thisTree, *parentTree;
    if ((baton->error_code = git_commit_tree(&thisTree, nextCommit)) != GIT_OK) {
      git_commit_free(nextCommit);
      break;
    }

    git_diff *diffs;
58 59 60 61
    git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
    char *file_path = strdup(baton->file_path);
    opts.pathspec.strings = &file_path;
    opts.pathspec.count = 1;
62 63
    git_commit *parent;
    unsigned int parents = git_commit_parentcount(nextCommit);
Tyler Wanek's avatar
Tyler Wanek committed
64 65 66 67
    if (parents > 1) {
      git_commit_free(nextCommit);
      continue;
    } else if (parents == 1) {
68 69 70 71 72 73
      if ((baton->error_code = git_commit_parent(&parent, nextCommit, 0)) != GIT_OK) {
        git_commit_free(nextCommit);
        break;
      }
      if (
        (baton->error_code = git_commit_tree(&parentTree, parent)) != GIT_OK ||
74
        (baton->error_code = git_diff_tree_to_tree(&diffs, repo, parentTree, thisTree, &opts)) != GIT_OK
75 76 77 78 79 80
      ) {
        git_commit_free(nextCommit);
        git_commit_free(parent);
        break;
      }
    } else {
81
      if ((baton->error_code = git_diff_tree_to_tree(&diffs, repo, NULL, thisTree, &opts)) != GIT_OK) {
82 83 84 85 86
        git_commit_free(nextCommit);
        break;
      }
    }

87 88 89 90
    free(file_path);
    opts.pathspec.strings = NULL;
    opts.pathspec.count = 0;

91
    bool flag = false;
92
    bool doRenamedPass = false;
93 94 95 96 97 98 99 100 101 102 103 104 105 106
    unsigned int numDeltas = git_diff_num_deltas(diffs);
    for (unsigned int j = 0; j < numDeltas; ++j) {
      git_patch *nextPatch;
      baton->error_code = git_patch_from_diff(&nextPatch, diffs, j);

      if (baton->error_code < GIT_OK) {
        break;
      }

      if (nextPatch == NULL) {
        continue;
      }

      const git_diff_delta *delta = git_patch_get_delta(nextPatch);
Tyler Wanek's avatar
Tyler Wanek committed
107 108
      bool isEqualOldFile = !strcmp(delta->old_file.path, baton->file_path);
      bool isEqualNewFile = !strcmp(delta->new_file.path, baton->file_path);
109 110

      if (isEqualNewFile) {
Tyler Wanek's avatar
Tyler Wanek committed
111
        if (delta->status == GIT_DELTA_ADDED || delta->status == GIT_DELTA_DELETED) {
112 113 114
          doRenamedPass = true;
          break;
        }
Tyler Wanek's avatar
Tyler Wanek committed
115 116 117 118
        std::pair<git_commit *, std::pair<char *, git_delta_t> > *historyEntry;
        if (!isEqualOldFile) {
          historyEntry = new std::pair<git_commit *, std::pair<char *, git_delta_t> >(
            nextCommit,
119
            std::pair<char *, git_delta_t>(strdup(delta->old_file.path), delta->status)
Tyler Wanek's avatar
Tyler Wanek committed
120 121 122
          );
        } else {
          historyEntry = new std::pair<git_commit *, std::pair<char *, git_delta_t> >(
123
            nextCommit,
124
            std::pair<char *, git_delta_t>(strdup(delta->new_file.path), delta->status)
Tyler Wanek's avatar
Tyler Wanek committed
125
          );
126
        }
Tyler Wanek's avatar
Tyler Wanek committed
127
        baton->out->push_back(historyEntry);
128 129 130 131 132 133 134 135 136 137
        flag = true;
      }

      git_patch_free(nextPatch);

      if (flag) {
        break;
      }
    }

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    if (doRenamedPass) {
      git_diff_free(diffs);

      if (parents == 1) {
        if ((baton->error_code = git_diff_tree_to_tree(&diffs, repo, parentTree, thisTree, NULL)) != GIT_OK) {
          git_commit_free(nextCommit);
          break;
        }
        if ((baton->error_code = git_diff_find_similar(diffs, NULL)) != GIT_OK) {
          git_commit_free(nextCommit);
          break;
        }
      } else {
        if ((baton->error_code = git_diff_tree_to_tree(&diffs, repo, NULL, thisTree, NULL)) != GIT_OK) {
          git_commit_free(nextCommit);
          break;
        }
        if((baton->error_code = git_diff_find_similar(diffs, NULL)) != GIT_OK) {
          git_commit_free(nextCommit);
          break;
        }
      }

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
      flag = false;
      numDeltas = git_diff_num_deltas(diffs);
      for (unsigned int j = 0; j < numDeltas; ++j) {
        git_patch *nextPatch;
        baton->error_code = git_patch_from_diff(&nextPatch, diffs, j);

        if (baton->error_code < GIT_OK) {
          break;
        }

        if (nextPatch == NULL) {
          continue;
        }

        const git_diff_delta *delta = git_patch_get_delta(nextPatch);
        bool isEqualOldFile = !strcmp(delta->old_file.path, baton->file_path);
        bool isEqualNewFile = !strcmp(delta->new_file.path, baton->file_path);
178 179 180 181 182 183 184
        int oldLen = strlen(delta->old_file.path);
        int newLen = strlen(delta->new_file.path);
        char *outPair = new char[oldLen + newLen + 2];
        strcpy(outPair, delta->new_file.path);
        outPair[newLen] = '\n';
        outPair[newLen + 1] = '\0';
        strcat(outPair, delta->old_file.path);
185 186 187 188 189 190

        if (isEqualNewFile) {
          std::pair<git_commit *, std::pair<char *, git_delta_t> > *historyEntry;
          if (!isEqualOldFile) {
            historyEntry = new std::pair<git_commit *, std::pair<char *, git_delta_t> >(
              nextCommit,
191
              std::pair<char *, git_delta_t>(strdup(outPair), delta->status)
192 193 194 195 196 197 198 199 200
            );
          } else {
            historyEntry = new std::pair<git_commit *, std::pair<char *, git_delta_t> >(
              nextCommit,
              std::pair<char *, git_delta_t>(strdup(delta->new_file.path), delta->status)
            );
          }
          baton->out->push_back(historyEntry);
          flag = true;
Tyler Wanek's avatar
Tyler Wanek committed
201 202 203 204
        } else if (isEqualOldFile) {
          std::pair<git_commit *, std::pair<char *, git_delta_t> > *historyEntry;
          historyEntry = new std::pair<git_commit *, std::pair<char *, git_delta_t> >(
            nextCommit,
205
            std::pair<char *, git_delta_t>(strdup(outPair), delta->status)
Tyler Wanek's avatar
Tyler Wanek committed
206 207 208
          );
          baton->out->push_back(historyEntry);
          flag = true;
209 210
        }

211 212
        delete[] outPair;

213 214 215 216 217 218 219 220
        git_patch_free(nextPatch);

        if (flag) {
          break;
        }
      }
    }

Tyler Wanek's avatar
Tyler Wanek committed
221 222
    git_diff_free(diffs);

223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    if (!flag && nextCommit != NULL) {
      git_commit_free(nextCommit);
    }

    if (baton->error_code != GIT_OK) {
      break;
    }
  }

  free(nextOid);

  if (baton->error_code != GIT_OK) {
    if (baton->error_code != GIT_ITEROVER) {
      baton->error = git_error_dup(giterr_last());

      while(!baton->out->empty())
      {
Tyler Wanek's avatar
Tyler Wanek committed
240
        std::pair<git_commit *, std::pair<char *, git_delta_t> > *pairToFree = baton->out->back();
241 242
        baton->out->pop_back();
        git_commit_free(pairToFree->first);
Tyler Wanek's avatar
Tyler Wanek committed
243
        free(pairToFree->second.first);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
        free(pairToFree);
      }

      delete baton->out;

      baton->out = NULL;
    }
  } else {
    baton->error_code = GIT_OK;
  }
}

void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback()
{
  if (baton->out != NULL) {
    unsigned int size = baton->out->size();
    Local<Array> result = Nan::New<Array>(size);
    for (unsigned int i = 0; i < size; i++) {
      Local<v8::Object> historyEntry = Nan::New<Object>();
Tyler Wanek's avatar
Tyler Wanek committed
263
      std::pair<git_commit *, std::pair<char *, git_delta_t> > *batonResult = baton->out->at(i);
264
      Nan::Set(historyEntry, Nan::New("commit").ToLocalChecked(), GitCommit::New(batonResult->first, true));
Tyler Wanek's avatar
Tyler Wanek committed
265 266
      Nan::Set(historyEntry, Nan::New("status").ToLocalChecked(), Nan::New<Number>(batonResult->second.second));
      if (batonResult->second.second == GIT_DELTA_RENAMED) {
267 268 269 270 271 272 273
        char *namePair = batonResult->second.first;
        char *split = strchr(namePair, '\n');
        *split = '\0';
        char *oldName = split + 1;

        Nan::Set(historyEntry, Nan::New("oldName").ToLocalChecked(), Nan::New(oldName).ToLocalChecked());
        Nan::Set(historyEntry, Nan::New("newName").ToLocalChecked(), Nan::New(namePair).ToLocalChecked());
Tyler Wanek's avatar
Tyler Wanek committed
274
      }
275 276
      Nan::Set(result, Nan::New<Number>(i), historyEntry);

Tyler Wanek's avatar
Tyler Wanek committed
277
      free(batonResult->second.first);
278 279 280 281 282 283 284 285 286 287 288 289 290 291
      free(batonResult);
    }

    Local<v8::Value> argv[2] = {
      Nan::Null(),
      result
    };
    callback->Call(2, argv);

    delete baton->out;
    return;
  }

  if (baton->error) {
292 293 294 295 296 297 298
    Local<v8::Object> err;
    if (baton->error->message) {
      err = Nan::Error(baton->error->message)->ToObject();
    } else {
      err = Nan::Error("Method fileHistoryWalk has thrown an error.")->ToObject();
    }
    err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code));
299
    err->Set(Nan::New("errorFunction").ToLocalChecked(), Nan::New("Revwalk.fileHistoryWalk").ToLocalChecked());
300
    Local<v8::Value> argv[1] = {
301
      err
302 303 304 305 306 307 308 309 310 311 312 313 314 315
    };
    callback->Call(1, argv);
    if (baton->error->message)
    {
      free((void *)baton->error->message);
    }

    free((void *)baton->error);
    return;
  }

  if (baton->error_code < 0) {
    Local<v8::Object> err = Nan::Error("Method next has thrown an error.")->ToObject();
    err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code));
316
    err->Set(Nan::New("errorFunction").ToLocalChecked(), Nan::New("Revwalk.fileHistoryWalk").ToLocalChecked());
317 318 319 320 321 322 323 324 325
    Local<v8::Value> argv[1] = {
      err
    };
    callback->Call(1, argv);
    return;
  }

  callback->Call(0, NULL);
}